From 9f566e4c5495fdc6efd5d383482811ab05de9e60 Mon Sep 17 00:00:00 2001 From: deliciousmonster Date: Mon, 3 Feb 2020 10:37:53 -0700 Subject: [PATCH 01/19] Update package.json --- package.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/package.json b/package.json index 798f7685d..4dc41625a 100644 --- a/package.json +++ b/package.json @@ -7,9 +7,6 @@ "scripts": { "build": "webpack --mode production --config webpack.prod.js", "clean-source": "rm ./public/hdb.css && rm ./public/hdb.js", - "clean-destination": "rm -rf /Users/jrepp/Developer/harperdb/harperdb/docs/*", - "transfer": "cp -r ./public/* /Users/jrepp/Developer/harperdb/harperdb/docs/", - "tohdb": "npm run build && npm run clean-source && npm run clean-destination && npm run transfer", "start": "webpack-dev-server --mode development --config webpack.dev.js", "lint": "eslint --fix src && sass-lint --verbose", "deploy": "npm run build && npm run clean-source && scp -r ./public/* ubuntu@fcfleet.demo.harperdb.io:/home/ubuntu/sites/studio-stage" From 8c91d8a84f91437221cd276a5407c1475d3af635 Mon Sep 17 00:00:00 2001 From: deliciousmonster Date: Mon, 3 Feb 2020 11:22:20 -0700 Subject: [PATCH 02/19] Update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4dc41625a..6e261d8fa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "harperdb-studio", - "version": "2.0.0", + "version": "3.0.0", "description": "A UI for HarperDB", "deploymentUrl": "studio.harperdb.io", "private": true, From 76b2b6422abe95cdac054862a9d40b9e658a90ec Mon Sep 17 00:00:00 2001 From: deliciousmonster Date: Mon, 3 Feb 2020 12:07:42 -0700 Subject: [PATCH 03/19] clean up for lint --- .eslintrc | 2 +- .sass-lint.yml | 2 +- src/app.scss | 1 - src/assets/styles/_card.scss | 3 ++ src/assets/styles/_csv-input.scss | 2 +- src/assets/styles/_login.scss | 4 +- src/assets/styles/_network.scss | 38 ------------------- src/assets/styles/_react-table.scss | 1 + src/components/instance/browse/index.js | 2 +- .../instance/clustering/clusterCard.js | 4 +- src/components/instance/clustering/index.js | 3 +- src/util/queryLMS.js | 1 + 12 files changed, 14 insertions(+), 49 deletions(-) delete mode 100644 src/assets/styles/_network.scss diff --git a/.eslintrc b/.eslintrc index db98d2e42..71b24681d 100644 --- a/.eslintrc +++ b/.eslintrc @@ -28,6 +28,6 @@ "jsx-a11y/click-events-have-key-events": 0, "jsx-a11y/anchor-is-valid": 0, "no-param-reassign": 0, - "max-lines": ["warn", {"max": 100, "skipComments": false}] + "max-lines": ["warn", {"max": 150, "skipComments": false}] } } diff --git a/.sass-lint.yml b/.sass-lint.yml index 315747d09..71ec2f288 100644 --- a/.sass-lint.yml +++ b/.sass-lint.yml @@ -21,7 +21,7 @@ rules: no-color-hex: 0 no-color-keywords: 1 no-color-literals: 0 - no-combinators: 1 + no-combinators: 0 no-css-comments: 1 no-debug: 1 no-disallowed-properties: 1 diff --git a/src/app.scss b/src/app.scss index 9dd5f2f03..eaf823b90 100644 --- a/src/app.scss +++ b/src/app.scss @@ -15,5 +15,4 @@ @import './assets/styles/code'; @import './assets/styles/topnav'; @import './assets/styles/csv-input'; -@import './assets/styles/network'; diff --git a/src/assets/styles/_card.scss b/src/assets/styles/_card.scss index b31ec1160..a1c0e1c8a 100644 --- a/src/assets/styles/_card.scss +++ b/src/assets/styles/_card.scss @@ -24,12 +24,15 @@ .btn { border-color: transparent; } + input { background: $color-white; } + .btn { white-space: nowrap; } + &.error { input { border-color: $color-danger; diff --git a/src/assets/styles/_csv-input.scss b/src/assets/styles/_csv-input.scss index eba3e4474..ee0c910ad 100644 --- a/src/assets/styles/_csv-input.scss +++ b/src/assets/styles/_csv-input.scss @@ -1,4 +1,4 @@ -#csv-uploader{ +#csv-uploader { position: relative; height: 300px; border: 0; diff --git a/src/assets/styles/_login.scss b/src/assets/styles/_login.scss index c9f774f10..acd446b97 100644 --- a/src/assets/styles/_login.scss +++ b/src/assets/styles/_login.scss @@ -30,9 +30,9 @@ -webkit-text-fill-color: $color-white !important; } - ::placeholder { /* Chrome, Firefox, Opera, Safari 10.1+ */ + ::placeholder { color: $color-grey !important; - opacity: 0.5; /* Firefox */ + opacity: .5; } #login-error { diff --git a/src/assets/styles/_network.scss b/src/assets/styles/_network.scss deleted file mode 100644 index b787d1a38..000000000 --- a/src/assets/styles/_network.scss +++ /dev/null @@ -1,38 +0,0 @@ -#fabric { - height: 100%; - background-color: $light-white-overlay !important; - background-size: 50px 50px; - border-radius: 6px; - position:relative; - overflow: hidden; - - .fabric-node { - text-align: center; - - .node-scroller { - max-height: 75px; - overflow-x: hidden; - overflow-y: auto; - } - } - - .label-content { - padding: 5px 10px; - background: cornflowerblue; - color: white; - border-radius: 5px; - display: flex; - align-items: center; - justify-content: center; - font-size: 10px; - cursor: pointer; - } - - -} - -#fabric-editor.modal-dialog { - max-width: 800px !important; - -} - diff --git a/src/assets/styles/_react-table.scss b/src/assets/styles/_react-table.scss index 1e5269eb9..7e143acef 100755 --- a/src/assets/styles/_react-table.scss +++ b/src/assets/styles/_react-table.scss @@ -44,6 +44,7 @@ overflow: visible; } } + .-pagination { border-top: 1px solid rgba(0, 0, 0, .05); box-shadow: none; diff --git a/src/components/instance/browse/index.js b/src/components/instance/browse/index.js index a225ccd68..180a92d83 100644 --- a/src/components/instance/browse/index.js +++ b/src/components/instance/browse/index.js @@ -8,7 +8,7 @@ import EntityManager from '../shared/entityManager'; import JSONViewer from './jsonviewer'; import CSVUploader from './csvuploader'; -export default ({ auth, structure, network, refreshInstance }) => { +export default ({ auth, structure, refreshInstance }) => { const history = useHistory(); const { instance_id, schema, table, action } = useParams(); diff --git a/src/components/instance/clustering/clusterCard.js b/src/components/instance/clustering/clusterCard.js index e6071edbc..f77013f77 100644 --- a/src/components/instance/clustering/clusterCard.js +++ b/src/components/instance/clustering/clusterCard.js @@ -13,10 +13,10 @@ export default ({ id, name }) => {
- + - + diff --git a/src/components/instance/clustering/index.js b/src/components/instance/clustering/index.js index 1f7de8716..ccd146a3c 100644 --- a/src/components/instance/clustering/index.js +++ b/src/components/instance/clustering/index.js @@ -1,9 +1,8 @@ import React, { useEffect } from 'react'; -import { Code, Row, Col, Card, CardBody, Button } from '@nio/ui-kit'; +import { Code, Row, Col } from '@nio/ui-kit'; import { useParams } from 'react-router-dom'; import { useHistory } from 'react-router'; import stringify from 'json-stringify-pretty-compact'; -import { useAlert } from 'react-alert'; import EntityManager from '../shared/entityManager'; import useLMS from '../../../stores/lmsData'; diff --git a/src/util/queryLMS.js b/src/util/queryLMS.js index d4186fdee..2013d1db5 100644 --- a/src/util/queryLMS.js +++ b/src/util/queryLMS.js @@ -1,5 +1,6 @@ import instances from '../../mock_data/LMS_API.instances.json'; +// eslint-disable-next-line no-unused-vars export default async ({ endpoint, method, payload, auth }) => { /* const request = await fetch( From eb4f815e2561c45f7e3d07b20f1dd512ea5bad8d Mon Sep 17 00:00:00 2001 From: deliciousmonster Date: Wed, 5 Feb 2020 11:59:47 -0700 Subject: [PATCH 04/19] update packages --- package-lock.json | 738 ++++++++++++++++++++++++++++++++-------------- package.json | 20 +- 2 files changed, 519 insertions(+), 239 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2a722f02f..1cf578d79 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,13 +1,13 @@ { "name": "harperdb-studio", - "version": "2.0.0", + "version": "3.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { "@babel/cli": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.8.3.tgz", - "integrity": "sha512-K2UXPZCKMv7KwWy9Bl4sa6+jTNP7JyDiHKzoOiUUygaEDbC60vaargZDnO9oFMvlq8pIKOOyUUgeMYrsaN9djA==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.8.4.tgz", + "integrity": "sha512-XXLgAm6LBbaNxaGhMAznXXaxtCWfuv6PIDJ9Alsy9JYTOh+j2jJz+L/162kkfU1j/pTSxK1xGmlwI4pdIMkoag==", "dev": true, "requires": { "chokidar": "^2.1.8", @@ -22,9 +22,9 @@ }, "dependencies": { "commander": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.0.tgz", - "integrity": "sha512-NIQrwvv9V39FHgGFm36+U9SMQzbiHvU79k+iADraJTpmrFFfx7Ds0IvDoAdZsDrknlkRk14OYoWXb57uTh7/sw==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", "dev": true } } @@ -39,37 +39,37 @@ } }, "@babel/compat-data": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.8.1.tgz", - "integrity": "sha512-Z+6ZOXvyOWYxJ50BwxzdhRnRsGST8Y3jaZgxYig575lTjVSs3KtJnmESwZegg6e2Dn0td1eDhoWlp1wI4BTCPw==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.8.5.tgz", + "integrity": "sha512-jWYUqQX/ObOhG1UiEkbH5SANsE/8oKXiQWjj7p7xgj9Zmnt//aUvyz4dBkK0HNsS8/cbyC5NmmH87VekW+mXFg==", "dev": true, "requires": { - "browserslist": "^4.8.2", + "browserslist": "^4.8.5", "invariant": "^2.2.4", "semver": "^5.5.0" }, "dependencies": { "browserslist": { - "version": "4.8.5", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.8.5.tgz", - "integrity": "sha512-4LMHuicxkabIB+n9874jZX/az1IaZ5a+EUuvD7KFOu9x/Bd5YHyO0DIz2ls/Kl8g0ItS4X/ilEgf4T1Br0lgSg==", + "version": "4.8.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.8.6.tgz", + "integrity": "sha512-ZHao85gf0eZ0ESxLfCp73GG9O/VTytYDIkIiZDlURppLTI9wErSM/5yAKEq6rcUdxBLjMELmrYUJGg5sxGKMHg==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001022", - "electron-to-chromium": "^1.3.338", - "node-releases": "^1.1.46" + "caniuse-lite": "^1.0.30001023", + "electron-to-chromium": "^1.3.341", + "node-releases": "^1.1.47" } }, "caniuse-lite": { - "version": "1.0.30001022", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001022.tgz", - "integrity": "sha512-FjwPPtt/I07KyLPkBQ0g7/XuZg6oUkYBVnPHNj3VHJbOjmmJ/GdSo/GUY6MwINEQvjhP6WZVbX8Tvms8xh0D5A==", + "version": "1.0.30001025", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001025.tgz", + "integrity": "sha512-SKyFdHYfXUZf5V85+PJgLYyit27q4wgvZuf8QTOk1osbypcROihMBlx9GRar2/pIcKH2r4OehdlBr9x6PXetAQ==", "dev": true }, "electron-to-chromium": { - "version": "1.3.339", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.339.tgz", - "integrity": "sha512-C1i/vH6/kQx9YV8RddMkmW216GwW4pTrnYIlKmDFIqXA4fPwqDxIdGyHsuG+fgurHoljRz7/oaD+tztcryW/9g==", + "version": "1.3.345", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.345.tgz", + "integrity": "sha512-f8nx53+Z9Y+SPWGg3YdHrbYYfIJAtbUjpFfW4X1RwTZ94iUG7geg9tV8HqzAXX7XTNgyWgAFvce4yce8ZKxKmg==", "dev": true }, "node-releases": { @@ -92,17 +92,17 @@ } }, "@babel/core": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.8.3.tgz", - "integrity": "sha512-4XFkf8AwyrEG7Ziu3L2L0Cv+WyY47Tcsp70JFmpftbAA1K7YL/sgE9jh9HyNj08Y/U50ItUchpN0w6HxAoX1rA==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.8.4.tgz", + "integrity": "sha512-0LiLrB2PwrVI+a2/IEskBopDYSd8BCb3rOvH7D5tzoWd696TBEduBvuLVm4Nx6rltrLZqvI3MCalB2K2aVzQjA==", "dev": true, "requires": { "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.8.3", - "@babel/helpers": "^7.8.3", - "@babel/parser": "^7.8.3", + "@babel/generator": "^7.8.4", + "@babel/helpers": "^7.8.4", + "@babel/parser": "^7.8.4", "@babel/template": "^7.8.3", - "@babel/traverse": "^7.8.3", + "@babel/traverse": "^7.8.4", "@babel/types": "^7.8.3", "convert-source-map": "^1.7.0", "debug": "^4.1.0", @@ -124,9 +124,9 @@ } }, "@babel/generator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.3.tgz", - "integrity": "sha512-WjoPk8hRpDRqqzRpvaR8/gDUPkrnOOeuT2m8cNICJtZH6mwaCo3v0OKMI7Y6SM1pBtyijnLtAL0HDi41pf41ug==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.4.tgz", + "integrity": "sha512-PwhclGdRpNAf3IxZb0YVuITPZmmrXz9zf6fH8lT4XbrmfQKr6ryBzhv593P5C6poJRciFCL/eHGW2NuGrgEyxA==", "dev": true, "requires": { "@babel/types": "^7.8.3", @@ -176,9 +176,9 @@ } }, "@babel/parser": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.3.tgz", - "integrity": "sha512-/V72F4Yp/qmHaTALizEm9Gf2eQHV3QyTL3K0cNfijwnMnb1L+LDlAubb/ZnSdGAVzVSWakujHYs1I26x66sMeQ==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", + "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", "dev": true }, "@babel/template": { @@ -193,16 +193,16 @@ } }, "@babel/traverse": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.3.tgz", - "integrity": "sha512-we+a2lti+eEImHmEXp7bM9cTxGzxPmBiVJlLVD+FuuQMeeO7RaDbutbgeheDkw+Xe3mCfJHnGOWLswT74m2IPg==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.4.tgz", + "integrity": "sha512-NGLJPZwnVEyBPLI+bl9y9aSnxMhsKz42so7ApAv9D+b4vAFPpY013FTS9LdKxcABoIYFU52HcYga1pPlx454mg==", "dev": true, "requires": { "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.8.3", + "@babel/generator": "^7.8.4", "@babel/helper-function-name": "^7.8.3", "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/parser": "^7.8.3", + "@babel/parser": "^7.8.4", "@babel/types": "^7.8.3", "debug": "^4.1.0", "globals": "^11.1.0", @@ -325,9 +325,9 @@ } }, "@babel/generator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.3.tgz", - "integrity": "sha512-WjoPk8hRpDRqqzRpvaR8/gDUPkrnOOeuT2m8cNICJtZH6mwaCo3v0OKMI7Y6SM1pBtyijnLtAL0HDi41pf41ug==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.4.tgz", + "integrity": "sha512-PwhclGdRpNAf3IxZb0YVuITPZmmrXz9zf6fH8lT4XbrmfQKr6ryBzhv593P5C6poJRciFCL/eHGW2NuGrgEyxA==", "dev": true, "requires": { "@babel/types": "^7.8.3", @@ -377,9 +377,9 @@ } }, "@babel/parser": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.3.tgz", - "integrity": "sha512-/V72F4Yp/qmHaTALizEm9Gf2eQHV3QyTL3K0cNfijwnMnb1L+LDlAubb/ZnSdGAVzVSWakujHYs1I26x66sMeQ==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", + "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", "dev": true }, "@babel/template": { @@ -394,16 +394,16 @@ } }, "@babel/traverse": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.3.tgz", - "integrity": "sha512-we+a2lti+eEImHmEXp7bM9cTxGzxPmBiVJlLVD+FuuQMeeO7RaDbutbgeheDkw+Xe3mCfJHnGOWLswT74m2IPg==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.4.tgz", + "integrity": "sha512-NGLJPZwnVEyBPLI+bl9y9aSnxMhsKz42so7ApAv9D+b4vAFPpY013FTS9LdKxcABoIYFU52HcYga1pPlx454mg==", "dev": true, "requires": { "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.8.3", + "@babel/generator": "^7.8.4", "@babel/helper-function-name": "^7.8.3", "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/parser": "^7.8.3", + "@babel/parser": "^7.8.4", "@babel/types": "^7.8.3", "debug": "^4.1.0", "globals": "^11.1.0", @@ -457,39 +457,39 @@ } }, "@babel/helper-compilation-targets": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.8.3.tgz", - "integrity": "sha512-JLylPCsFjhLN+6uBSSh3iYdxKdeO9MNmoY96PE/99d8kyBFaXLORtAVhqN6iHa+wtPeqxKLghDOZry0+Aiw9Tw==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.8.4.tgz", + "integrity": "sha512-3k3BsKMvPp5bjxgMdrFyq0UaEO48HciVrOVF0+lon8pp95cyJ2ujAh0TrBHNMnJGT2rr0iKOJPFFbSqjDyf/Pg==", "dev": true, "requires": { - "@babel/compat-data": "^7.8.1", - "browserslist": "^4.8.2", + "@babel/compat-data": "^7.8.4", + "browserslist": "^4.8.5", "invariant": "^2.2.4", - "levenary": "^1.1.0", + "levenary": "^1.1.1", "semver": "^5.5.0" }, "dependencies": { "browserslist": { - "version": "4.8.5", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.8.5.tgz", - "integrity": "sha512-4LMHuicxkabIB+n9874jZX/az1IaZ5a+EUuvD7KFOu9x/Bd5YHyO0DIz2ls/Kl8g0ItS4X/ilEgf4T1Br0lgSg==", + "version": "4.8.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.8.6.tgz", + "integrity": "sha512-ZHao85gf0eZ0ESxLfCp73GG9O/VTytYDIkIiZDlURppLTI9wErSM/5yAKEq6rcUdxBLjMELmrYUJGg5sxGKMHg==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001022", - "electron-to-chromium": "^1.3.338", - "node-releases": "^1.1.46" + "caniuse-lite": "^1.0.30001023", + "electron-to-chromium": "^1.3.341", + "node-releases": "^1.1.47" } }, "caniuse-lite": { - "version": "1.0.30001022", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001022.tgz", - "integrity": "sha512-FjwPPtt/I07KyLPkBQ0g7/XuZg6oUkYBVnPHNj3VHJbOjmmJ/GdSo/GUY6MwINEQvjhP6WZVbX8Tvms8xh0D5A==", + "version": "1.0.30001025", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001025.tgz", + "integrity": "sha512-SKyFdHYfXUZf5V85+PJgLYyit27q4wgvZuf8QTOk1osbypcROihMBlx9GRar2/pIcKH2r4OehdlBr9x6PXetAQ==", "dev": true }, "electron-to-chromium": { - "version": "1.3.339", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.339.tgz", - "integrity": "sha512-C1i/vH6/kQx9YV8RddMkmW216GwW4pTrnYIlKmDFIqXA4fPwqDxIdGyHsuG+fgurHoljRz7/oaD+tztcryW/9g==", + "version": "1.3.345", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.345.tgz", + "integrity": "sha512-f8nx53+Z9Y+SPWGg3YdHrbYYfIJAtbUjpFfW4X1RwTZ94iUG7geg9tV8HqzAXX7XTNgyWgAFvce4yce8ZKxKmg==", "dev": true }, "node-releases": { @@ -573,9 +573,9 @@ } }, "@babel/parser": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.3.tgz", - "integrity": "sha512-/V72F4Yp/qmHaTALizEm9Gf2eQHV3QyTL3K0cNfijwnMnb1L+LDlAubb/ZnSdGAVzVSWakujHYs1I26x66sMeQ==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", + "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", "dev": true }, "@babel/template": { @@ -640,9 +640,9 @@ } }, "@babel/generator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.3.tgz", - "integrity": "sha512-WjoPk8hRpDRqqzRpvaR8/gDUPkrnOOeuT2m8cNICJtZH6mwaCo3v0OKMI7Y6SM1pBtyijnLtAL0HDi41pf41ug==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.4.tgz", + "integrity": "sha512-PwhclGdRpNAf3IxZb0YVuITPZmmrXz9zf6fH8lT4XbrmfQKr6ryBzhv593P5C6poJRciFCL/eHGW2NuGrgEyxA==", "dev": true, "requires": { "@babel/types": "^7.8.3", @@ -692,9 +692,9 @@ } }, "@babel/parser": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.3.tgz", - "integrity": "sha512-/V72F4Yp/qmHaTALizEm9Gf2eQHV3QyTL3K0cNfijwnMnb1L+LDlAubb/ZnSdGAVzVSWakujHYs1I26x66sMeQ==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", + "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", "dev": true }, "@babel/template": { @@ -709,16 +709,16 @@ } }, "@babel/traverse": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.3.tgz", - "integrity": "sha512-we+a2lti+eEImHmEXp7bM9cTxGzxPmBiVJlLVD+FuuQMeeO7RaDbutbgeheDkw+Xe3mCfJHnGOWLswT74m2IPg==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.4.tgz", + "integrity": "sha512-NGLJPZwnVEyBPLI+bl9y9aSnxMhsKz42so7ApAv9D+b4vAFPpY013FTS9LdKxcABoIYFU52HcYga1pPlx454mg==", "dev": true, "requires": { "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.8.3", + "@babel/generator": "^7.8.4", "@babel/helper-function-name": "^7.8.3", "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/parser": "^7.8.3", + "@babel/parser": "^7.8.4", "@babel/types": "^7.8.3", "debug": "^4.1.0", "globals": "^11.1.0", @@ -862,9 +862,9 @@ } }, "@babel/parser": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.3.tgz", - "integrity": "sha512-/V72F4Yp/qmHaTALizEm9Gf2eQHV3QyTL3K0cNfijwnMnb1L+LDlAubb/ZnSdGAVzVSWakujHYs1I26x66sMeQ==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", + "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", "dev": true }, "@babel/template": { @@ -956,9 +956,9 @@ } }, "@babel/generator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.3.tgz", - "integrity": "sha512-WjoPk8hRpDRqqzRpvaR8/gDUPkrnOOeuT2m8cNICJtZH6mwaCo3v0OKMI7Y6SM1pBtyijnLtAL0HDi41pf41ug==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.4.tgz", + "integrity": "sha512-PwhclGdRpNAf3IxZb0YVuITPZmmrXz9zf6fH8lT4XbrmfQKr6ryBzhv593P5C6poJRciFCL/eHGW2NuGrgEyxA==", "dev": true, "requires": { "@babel/types": "^7.8.3", @@ -1008,9 +1008,9 @@ } }, "@babel/parser": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.3.tgz", - "integrity": "sha512-/V72F4Yp/qmHaTALizEm9Gf2eQHV3QyTL3K0cNfijwnMnb1L+LDlAubb/ZnSdGAVzVSWakujHYs1I26x66sMeQ==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", + "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", "dev": true }, "@babel/template": { @@ -1025,16 +1025,16 @@ } }, "@babel/traverse": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.3.tgz", - "integrity": "sha512-we+a2lti+eEImHmEXp7bM9cTxGzxPmBiVJlLVD+FuuQMeeO7RaDbutbgeheDkw+Xe3mCfJHnGOWLswT74m2IPg==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.4.tgz", + "integrity": "sha512-NGLJPZwnVEyBPLI+bl9y9aSnxMhsKz42so7ApAv9D+b4vAFPpY013FTS9LdKxcABoIYFU52HcYga1pPlx454mg==", "dev": true, "requires": { "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.8.3", + "@babel/generator": "^7.8.4", "@babel/helper-function-name": "^7.8.3", "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/parser": "^7.8.3", + "@babel/parser": "^7.8.4", "@babel/types": "^7.8.3", "debug": "^4.1.0", "globals": "^11.1.0", @@ -1109,9 +1109,9 @@ } }, "@babel/generator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.3.tgz", - "integrity": "sha512-WjoPk8hRpDRqqzRpvaR8/gDUPkrnOOeuT2m8cNICJtZH6mwaCo3v0OKMI7Y6SM1pBtyijnLtAL0HDi41pf41ug==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.4.tgz", + "integrity": "sha512-PwhclGdRpNAf3IxZb0YVuITPZmmrXz9zf6fH8lT4XbrmfQKr6ryBzhv593P5C6poJRciFCL/eHGW2NuGrgEyxA==", "dev": true, "requires": { "@babel/types": "^7.8.3", @@ -1161,9 +1161,9 @@ } }, "@babel/parser": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.3.tgz", - "integrity": "sha512-/V72F4Yp/qmHaTALizEm9Gf2eQHV3QyTL3K0cNfijwnMnb1L+LDlAubb/ZnSdGAVzVSWakujHYs1I26x66sMeQ==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", + "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", "dev": true }, "@babel/template": { @@ -1178,16 +1178,16 @@ } }, "@babel/traverse": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.3.tgz", - "integrity": "sha512-we+a2lti+eEImHmEXp7bM9cTxGzxPmBiVJlLVD+FuuQMeeO7RaDbutbgeheDkw+Xe3mCfJHnGOWLswT74m2IPg==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.4.tgz", + "integrity": "sha512-NGLJPZwnVEyBPLI+bl9y9aSnxMhsKz42so7ApAv9D+b4vAFPpY013FTS9LdKxcABoIYFU52HcYga1pPlx454mg==", "dev": true, "requires": { "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.8.3", + "@babel/generator": "^7.8.4", "@babel/helper-function-name": "^7.8.3", "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/parser": "^7.8.3", + "@babel/parser": "^7.8.4", "@babel/types": "^7.8.3", "debug": "^4.1.0", "globals": "^11.1.0", @@ -1271,9 +1271,9 @@ } }, "@babel/parser": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.3.tgz", - "integrity": "sha512-/V72F4Yp/qmHaTALizEm9Gf2eQHV3QyTL3K0cNfijwnMnb1L+LDlAubb/ZnSdGAVzVSWakujHYs1I26x66sMeQ==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", + "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", "dev": true }, "@babel/template": { @@ -1349,9 +1349,9 @@ } }, "@babel/generator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.3.tgz", - "integrity": "sha512-WjoPk8hRpDRqqzRpvaR8/gDUPkrnOOeuT2m8cNICJtZH6mwaCo3v0OKMI7Y6SM1pBtyijnLtAL0HDi41pf41ug==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.4.tgz", + "integrity": "sha512-PwhclGdRpNAf3IxZb0YVuITPZmmrXz9zf6fH8lT4XbrmfQKr6ryBzhv593P5C6poJRciFCL/eHGW2NuGrgEyxA==", "dev": true, "requires": { "@babel/types": "^7.8.3", @@ -1401,9 +1401,9 @@ } }, "@babel/parser": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.3.tgz", - "integrity": "sha512-/V72F4Yp/qmHaTALizEm9Gf2eQHV3QyTL3K0cNfijwnMnb1L+LDlAubb/ZnSdGAVzVSWakujHYs1I26x66sMeQ==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", + "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", "dev": true }, "@babel/template": { @@ -1418,16 +1418,16 @@ } }, "@babel/traverse": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.3.tgz", - "integrity": "sha512-we+a2lti+eEImHmEXp7bM9cTxGzxPmBiVJlLVD+FuuQMeeO7RaDbutbgeheDkw+Xe3mCfJHnGOWLswT74m2IPg==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.4.tgz", + "integrity": "sha512-NGLJPZwnVEyBPLI+bl9y9aSnxMhsKz42so7ApAv9D+b4vAFPpY013FTS9LdKxcABoIYFU52HcYga1pPlx454mg==", "dev": true, "requires": { "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.8.3", + "@babel/generator": "^7.8.4", "@babel/helper-function-name": "^7.8.3", "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/parser": "^7.8.3", + "@babel/parser": "^7.8.4", "@babel/types": "^7.8.3", "debug": "^4.1.0", "globals": "^11.1.0", @@ -1481,13 +1481,13 @@ } }, "@babel/helpers": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.8.3.tgz", - "integrity": "sha512-LmU3q9Pah/XyZU89QvBgGt+BCsTPoQa+73RxAQh8fb8qkDyIfeQnmgs+hvzhTCKTzqOyk7JTkS3MS1S8Mq5yrQ==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.8.4.tgz", + "integrity": "sha512-VPbe7wcQ4chu4TDQjimHv/5tj73qz88o12EPkO2ValS2QiQS/1F2SsjyIGNnAD0vF/nZS6Cf9i+vW6HIlnaR8w==", "dev": true, "requires": { "@babel/template": "^7.8.3", - "@babel/traverse": "^7.8.3", + "@babel/traverse": "^7.8.4", "@babel/types": "^7.8.3" }, "dependencies": { @@ -1501,9 +1501,9 @@ } }, "@babel/generator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.3.tgz", - "integrity": "sha512-WjoPk8hRpDRqqzRpvaR8/gDUPkrnOOeuT2m8cNICJtZH6mwaCo3v0OKMI7Y6SM1pBtyijnLtAL0HDi41pf41ug==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.4.tgz", + "integrity": "sha512-PwhclGdRpNAf3IxZb0YVuITPZmmrXz9zf6fH8lT4XbrmfQKr6ryBzhv593P5C6poJRciFCL/eHGW2NuGrgEyxA==", "dev": true, "requires": { "@babel/types": "^7.8.3", @@ -1553,9 +1553,9 @@ } }, "@babel/parser": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.3.tgz", - "integrity": "sha512-/V72F4Yp/qmHaTALizEm9Gf2eQHV3QyTL3K0cNfijwnMnb1L+LDlAubb/ZnSdGAVzVSWakujHYs1I26x66sMeQ==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", + "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", "dev": true }, "@babel/template": { @@ -1570,16 +1570,16 @@ } }, "@babel/traverse": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.3.tgz", - "integrity": "sha512-we+a2lti+eEImHmEXp7bM9cTxGzxPmBiVJlLVD+FuuQMeeO7RaDbutbgeheDkw+Xe3mCfJHnGOWLswT74m2IPg==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.4.tgz", + "integrity": "sha512-NGLJPZwnVEyBPLI+bl9y9aSnxMhsKz42so7ApAv9D+b4vAFPpY013FTS9LdKxcABoIYFU52HcYga1pPlx454mg==", "dev": true, "requires": { "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.8.3", + "@babel/generator": "^7.8.4", "@babel/helper-function-name": "^7.8.3", "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/parser": "^7.8.3", + "@babel/parser": "^7.8.4", "@babel/types": "^7.8.3", "debug": "^4.1.0", "globals": "^11.1.0", @@ -1947,9 +1947,9 @@ } }, "@babel/parser": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.3.tgz", - "integrity": "sha512-/V72F4Yp/qmHaTALizEm9Gf2eQHV3QyTL3K0cNfijwnMnb1L+LDlAubb/ZnSdGAVzVSWakujHYs1I26x66sMeQ==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", + "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", "dev": true }, "@babel/template": { @@ -2042,9 +2042,9 @@ } }, "@babel/plugin-transform-for-of": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.8.3.tgz", - "integrity": "sha512-ZjXznLNTxhpf4Q5q3x1NsngzGA38t9naWH8Gt+0qYZEJAcvPI9waSStSh56u19Ofjr7QmD0wUsQ8hw8s/p1VnA==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.8.4.tgz", + "integrity": "sha512-iAXNlOWvcYUYoV8YIxwS7TxGRJcxyl8eQCfT+A5j8sKUzRFvJdcyjp97jL2IghWSRDaL2PU2O2tX8Cu9dTBq5A==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.8.3" @@ -2101,9 +2101,9 @@ } }, "@babel/parser": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.3.tgz", - "integrity": "sha512-/V72F4Yp/qmHaTALizEm9Gf2eQHV3QyTL3K0cNfijwnMnb1L+LDlAubb/ZnSdGAVzVSWakujHYs1I26x66sMeQ==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", + "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", "dev": true }, "@babel/template": { @@ -2240,9 +2240,9 @@ } }, "@babel/plugin-transform-parameters": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.8.3.tgz", - "integrity": "sha512-/pqngtGb54JwMBZ6S/D3XYylQDFtGjWrnoCF4gXZOUpFV/ujbxnoNGNvDGu6doFWRPBveE72qTx/RRU44j5I/Q==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.8.4.tgz", + "integrity": "sha512-IsS3oTxeTsZlE5KqzTbcC2sV0P9pXdec53SU+Yxv7o/6dvGM5AkTotQKhoSffhNgZ/dftsSiOoxy7evCYJXzVA==", "dev": true, "requires": { "@babel/helper-call-delegate": "^7.8.3", @@ -2367,9 +2367,9 @@ } }, "@babel/plugin-transform-typeof-symbol": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.8.3.tgz", - "integrity": "sha512-3TrkKd4LPqm4jHs6nPtSDI/SV9Cm5PRJkHLUgTcqRQQTMAZ44ZaAdDZJtvWFSaRcvT0a1rTmJ5ZA5tDKjleF3g==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.8.4.tgz", + "integrity": "sha512-2QKyfjGdvuNfHsb7qnBBlKclbD4CfshH2KvDabiijLMGXPHJXGxtDzwIF7bQP+T0ysw8fYTtxPafgfs/c1Lrqg==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.8.3" @@ -2396,13 +2396,13 @@ } }, "@babel/preset-env": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.8.3.tgz", - "integrity": "sha512-Rs4RPL2KjSLSE2mWAx5/iCH+GC1ikKdxPrhnRS6PfFVaiZeom22VFKN4X8ZthyN61kAaR05tfXTbCvatl9WIQg==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.8.4.tgz", + "integrity": "sha512-HihCgpr45AnSOHRbS5cWNTINs0TwaR8BS8xIIH+QwiW8cKL0llV91njQMpeMReEPVs+1Ao0x3RLEBLtt1hOq4w==", "dev": true, "requires": { - "@babel/compat-data": "^7.8.0", - "@babel/helper-compilation-targets": "^7.8.3", + "@babel/compat-data": "^7.8.4", + "@babel/helper-compilation-targets": "^7.8.4", "@babel/helper-module-imports": "^7.8.3", "@babel/helper-plugin-utils": "^7.8.3", "@babel/plugin-proposal-async-generator-functions": "^7.8.3", @@ -2431,7 +2431,7 @@ "@babel/plugin-transform-dotall-regex": "^7.8.3", "@babel/plugin-transform-duplicate-keys": "^7.8.3", "@babel/plugin-transform-exponentiation-operator": "^7.8.3", - "@babel/plugin-transform-for-of": "^7.8.3", + "@babel/plugin-transform-for-of": "^7.8.4", "@babel/plugin-transform-function-name": "^7.8.3", "@babel/plugin-transform-literals": "^7.8.3", "@babel/plugin-transform-member-expression-literals": "^7.8.3", @@ -2442,7 +2442,7 @@ "@babel/plugin-transform-named-capturing-groups-regex": "^7.8.3", "@babel/plugin-transform-new-target": "^7.8.3", "@babel/plugin-transform-object-super": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.8.4", "@babel/plugin-transform-property-literals": "^7.8.3", "@babel/plugin-transform-regenerator": "^7.8.3", "@babel/plugin-transform-reserved-words": "^7.8.3", @@ -2450,37 +2450,37 @@ "@babel/plugin-transform-spread": "^7.8.3", "@babel/plugin-transform-sticky-regex": "^7.8.3", "@babel/plugin-transform-template-literals": "^7.8.3", - "@babel/plugin-transform-typeof-symbol": "^7.8.3", + "@babel/plugin-transform-typeof-symbol": "^7.8.4", "@babel/plugin-transform-unicode-regex": "^7.8.3", "@babel/types": "^7.8.3", - "browserslist": "^4.8.2", + "browserslist": "^4.8.5", "core-js-compat": "^3.6.2", "invariant": "^2.2.2", - "levenary": "^1.1.0", + "levenary": "^1.1.1", "semver": "^5.5.0" }, "dependencies": { "browserslist": { - "version": "4.8.5", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.8.5.tgz", - "integrity": "sha512-4LMHuicxkabIB+n9874jZX/az1IaZ5a+EUuvD7KFOu9x/Bd5YHyO0DIz2ls/Kl8g0ItS4X/ilEgf4T1Br0lgSg==", + "version": "4.8.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.8.6.tgz", + "integrity": "sha512-ZHao85gf0eZ0ESxLfCp73GG9O/VTytYDIkIiZDlURppLTI9wErSM/5yAKEq6rcUdxBLjMELmrYUJGg5sxGKMHg==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001022", - "electron-to-chromium": "^1.3.338", - "node-releases": "^1.1.46" + "caniuse-lite": "^1.0.30001023", + "electron-to-chromium": "^1.3.341", + "node-releases": "^1.1.47" } }, "caniuse-lite": { - "version": "1.0.30001022", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001022.tgz", - "integrity": "sha512-FjwPPtt/I07KyLPkBQ0g7/XuZg6oUkYBVnPHNj3VHJbOjmmJ/GdSo/GUY6MwINEQvjhP6WZVbX8Tvms8xh0D5A==", + "version": "1.0.30001025", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001025.tgz", + "integrity": "sha512-SKyFdHYfXUZf5V85+PJgLYyit27q4wgvZuf8QTOk1osbypcROihMBlx9GRar2/pIcKH2r4OehdlBr9x6PXetAQ==", "dev": true }, "electron-to-chromium": { - "version": "1.3.339", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.339.tgz", - "integrity": "sha512-C1i/vH6/kQx9YV8RddMkmW216GwW4pTrnYIlKmDFIqXA4fPwqDxIdGyHsuG+fgurHoljRz7/oaD+tztcryW/9g==", + "version": "1.3.345", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.345.tgz", + "integrity": "sha512-f8nx53+Z9Y+SPWGg3YdHrbYYfIJAtbUjpFfW4X1RwTZ94iUG7geg9tV8HqzAXX7XTNgyWgAFvce4yce8ZKxKmg==", "dev": true }, "node-releases": { @@ -2516,9 +2516,9 @@ } }, "@babel/runtime": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.8.3.tgz", - "integrity": "sha512-fVHx1rzEmwB130VTkLnxR+HmxcTjGzH12LYQcFFoBwakMd3aOMD4OsRN7tGG/UOYE2ektgFrS8uACAoRk1CY0w==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.8.4.tgz", + "integrity": "sha512-neAp3zt80trRVBI1x0azq6c57aNBqYZH8KhMm3TaB7wEI5Q4A2SHfBHE8w9gOhI/lrqxtEbXZgQIrHP+wvSGwQ==", "requires": { "regenerator-runtime": "^0.13.2" } @@ -5200,26 +5200,26 @@ }, "dependencies": { "browserslist": { - "version": "4.8.5", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.8.5.tgz", - "integrity": "sha512-4LMHuicxkabIB+n9874jZX/az1IaZ5a+EUuvD7KFOu9x/Bd5YHyO0DIz2ls/Kl8g0ItS4X/ilEgf4T1Br0lgSg==", + "version": "4.8.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.8.6.tgz", + "integrity": "sha512-ZHao85gf0eZ0ESxLfCp73GG9O/VTytYDIkIiZDlURppLTI9wErSM/5yAKEq6rcUdxBLjMELmrYUJGg5sxGKMHg==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001022", - "electron-to-chromium": "^1.3.338", - "node-releases": "^1.1.46" + "caniuse-lite": "^1.0.30001023", + "electron-to-chromium": "^1.3.341", + "node-releases": "^1.1.47" } }, "caniuse-lite": { - "version": "1.0.30001022", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001022.tgz", - "integrity": "sha512-FjwPPtt/I07KyLPkBQ0g7/XuZg6oUkYBVnPHNj3VHJbOjmmJ/GdSo/GUY6MwINEQvjhP6WZVbX8Tvms8xh0D5A==", + "version": "1.0.30001025", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001025.tgz", + "integrity": "sha512-SKyFdHYfXUZf5V85+PJgLYyit27q4wgvZuf8QTOk1osbypcROihMBlx9GRar2/pIcKH2r4OehdlBr9x6PXetAQ==", "dev": true }, "electron-to-chromium": { - "version": "1.3.339", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.339.tgz", - "integrity": "sha512-C1i/vH6/kQx9YV8RddMkmW216GwW4pTrnYIlKmDFIqXA4fPwqDxIdGyHsuG+fgurHoljRz7/oaD+tztcryW/9g==", + "version": "1.3.345", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.345.tgz", + "integrity": "sha512-f8nx53+Z9Y+SPWGg3YdHrbYYfIJAtbUjpFfW4X1RwTZ94iUG7geg9tV8HqzAXX7XTNgyWgAFvce4yce8ZKxKmg==", "dev": true }, "node-releases": { @@ -6796,9 +6796,9 @@ } }, "eslint-plugin-import": { - "version": "2.20.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.20.0.tgz", - "integrity": "sha512-NK42oA0mUc8Ngn4kONOPsPB1XhbUvNHqF+g307dPV28aknPoiNnKLFd9em4nkswwepdF5ouieqv5Th/63U7YJQ==", + "version": "2.20.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.20.1.tgz", + "integrity": "sha512-qQHgFOTjguR+LnYRoToeZWT62XM55MBVXObHM6SKFd1VzDcX/vqT1kAz8ssqigh5eMj8qXcRoXXGZpPP6RfdCw==", "dev": true, "requires": { "array-includes": "^3.0.3", @@ -6910,9 +6910,9 @@ } }, "eslint-plugin-react": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.18.0.tgz", - "integrity": "sha512-p+PGoGeV4SaZRDsXqdj9OWcOrOpZn8gXoGPcIQTzo2IDMbAKhNDnME9myZWqO3Ic4R3YmwAZ1lDjWl2R2hMUVQ==", + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.18.3.tgz", + "integrity": "sha512-Bt56LNHAQCoou88s8ViKRjMB2+36XRejCQ1VoLj716KI1MoE99HpTVvIThJ0rvFmG4E4Gsq+UgToEjn+j044Bg==", "dev": true, "requires": { "array-includes": "^3.1.1", @@ -6923,7 +6923,8 @@ "object.fromentries": "^2.0.2", "object.values": "^1.1.1", "prop-types": "^15.7.2", - "resolve": "^1.14.2" + "resolve": "^1.14.2", + "string.prototype.matchall": "^4.0.2" }, "dependencies": { "array-includes": { @@ -7613,9 +7614,9 @@ } }, "follow-redirects": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.9.0.tgz", - "integrity": "sha512-CRcPzsSIbXyVDl0QI01muNDu69S8trU4jArW9LpOt2WtC6LyUJetcIrmfHsRBx7/Jb6GHJUiuqyYxPooFfNt6A==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.10.0.tgz", + "integrity": "sha512-4eyLK6s6lH32nOvLLwlIOnr9zrL8Sm+OvW4pVTJNoXeGzYIkHVf+pADQi+OJ0E67hiuSLezPVPyBcIZO50TmmQ==", "dev": true, "requires": { "debug": "^3.0.0" @@ -9214,6 +9215,96 @@ "ipaddr.js": "^1.9.0" } }, + "internal-slot": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.2.tgz", + "integrity": "sha512-2cQNfwhAfJIkU4KZPkDI+Gj5yNNnbqi40W9Gge6dfnk4TocEVm00B3bdiL+JINrbGJil2TeHvM4rETGzk/f/0g==", + "dev": true, + "requires": { + "es-abstract": "^1.17.0-next.1", + "has": "^1.0.3", + "side-channel": "^1.0.2" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.4", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.4.tgz", + "integrity": "sha512-Ae3um/gb8F0mui/jPL+QiqmglkUsaQf7FwBEHYIFkztkneosu9imhqHpBzQ3h1vit8t5iQ74t6PEVvphBZiuiQ==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.1.5", + "is-regex": "^1.0.5", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimleft": "^2.1.1", + "string.prototype.trimright": "^2.1.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true + }, + "is-callable": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", + "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==", + "dev": true + }, + "is-regex": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", + "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "object-inspect": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", + "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", + "dev": true + }, + "string.prototype.trimleft": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz", + "integrity": "sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + } + }, + "string.prototype.trimright": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz", + "integrity": "sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + } + } + } + }, "interpret": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz", @@ -9719,22 +9810,28 @@ } }, "jest-worker": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-24.9.0.tgz", - "integrity": "sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw==", + "version": "25.1.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-25.1.0.tgz", + "integrity": "sha512-ZHhHtlxOWSxCoNOKHGbiLzXnl42ga9CxDr27H36Qn+15pQZd3R/F24jrmjDelw9j/iHUIWMWs08/u2QN50HHOg==", "dev": true, "requires": { "merge-stream": "^2.0.0", - "supports-color": "^6.1.0" + "supports-color": "^7.0.0" }, "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "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==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "has-flag": "^4.0.0" } } } @@ -9910,9 +10007,9 @@ "dev": true }, "levenary": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/levenary/-/levenary-1.1.0.tgz", - "integrity": "sha512-VHcwhO0UTpUW7rLPN2/OiWJdgA1e9BqEDALhrgCe/F+uUJnep6CoUsTzMeP8Rh0NGr9uKquXxqe7lwLZo509nQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/levenary/-/levenary-1.1.1.tgz", + "integrity": "sha512-mkAdOIt79FD6irqjYSs4rdbnlT5vRonMEvBVPVb3XmevfS8kgRXwfes0dhPdEtzTWD/1eNE/Bm/G1iRt6DcnQQ==", "dev": true, "requires": { "leven": "^3.1.0" @@ -12129,12 +12226,12 @@ } }, "postcss-flexbugs-fixes": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-flexbugs-fixes/-/postcss-flexbugs-fixes-4.1.0.tgz", - "integrity": "sha512-jr1LHxQvStNNAHlgco6PzY308zvLklh7SJVYuWUwyUQncofaAlD2l+P/gxKHOdqWKe7xJSkVLFF/2Tp+JqMSZA==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-flexbugs-fixes/-/postcss-flexbugs-fixes-4.2.0.tgz", + "integrity": "sha512-QRE0n3hpkxxS/OGvzOa+PDuy4mh/Jg4o9ui22/ko5iGYOG3M5dfJabjnAZjTdh2G9F85c7Hv8hWcEDEKW/xceQ==", "dev": true, "requires": { - "postcss": "^7.0.0" + "postcss": "^7.0.26" }, "dependencies": { "ansi-styles": { @@ -12169,9 +12266,9 @@ } }, "postcss": { - "version": "7.0.21", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.21.tgz", - "integrity": "sha512-uIFtJElxJo29QC753JzhidoAhvp/e/Exezkdhfmt8AymWT6/5B7W1WmponYWkHk2eg6sONyTch0A3nkMPun3SQ==", + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.26.tgz", + "integrity": "sha512-IY4oRjpXWYshuTDFxMVkJDtWIk2LhsTlu8bZnbEJA4+bYT16Lvpo8Qv6EvDumhYRgzjZl489pmsY3qVgJQ08nA==", "dev": true, "requires": { "chalk": "^2.4.2", @@ -15658,6 +15755,95 @@ "integrity": "sha1-7GIRvtGSBEIIj+D3Cyg3Iy7SyKg=", "dev": true }, + "side-channel": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.2.tgz", + "integrity": "sha512-7rL9YlPHg7Ancea1S96Pa8/QWb4BtXL/TZvS6B8XFetGBeuhAsfmUspK6DokBeZ64+Kj9TCNRD/30pVz1BvQNA==", + "dev": true, + "requires": { + "es-abstract": "^1.17.0-next.1", + "object-inspect": "^1.7.0" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.4", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.4.tgz", + "integrity": "sha512-Ae3um/gb8F0mui/jPL+QiqmglkUsaQf7FwBEHYIFkztkneosu9imhqHpBzQ3h1vit8t5iQ74t6PEVvphBZiuiQ==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.1.5", + "is-regex": "^1.0.5", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimleft": "^2.1.1", + "string.prototype.trimright": "^2.1.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true + }, + "is-callable": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", + "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==", + "dev": true + }, + "is-regex": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", + "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "object-inspect": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", + "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", + "dev": true + }, + "string.prototype.trimleft": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz", + "integrity": "sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + } + }, + "string.prototype.trimright": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz", + "integrity": "sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + } + } + } + }, "signal-exit": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", @@ -16163,6 +16349,99 @@ "strip-ansi": "^3.0.0" } }, + "string.prototype.matchall": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.2.tgz", + "integrity": "sha512-N/jp6O5fMf9os0JU3E72Qhf590RSRZU/ungsL/qJUYVTNv7hTG0P/dbPjxINVN9jpscu3nzYwKESU3P3RY5tOg==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0", + "has-symbols": "^1.0.1", + "internal-slot": "^1.0.2", + "regexp.prototype.flags": "^1.3.0", + "side-channel": "^1.0.2" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.4", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.4.tgz", + "integrity": "sha512-Ae3um/gb8F0mui/jPL+QiqmglkUsaQf7FwBEHYIFkztkneosu9imhqHpBzQ3h1vit8t5iQ74t6PEVvphBZiuiQ==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.1.5", + "is-regex": "^1.0.5", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimleft": "^2.1.1", + "string.prototype.trimright": "^2.1.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true + }, + "is-callable": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", + "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==", + "dev": true + }, + "is-regex": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", + "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "object-inspect": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", + "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", + "dev": true + }, + "string.prototype.trimleft": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz", + "integrity": "sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + } + }, + "string.prototype.trimright": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz", + "integrity": "sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + } + } + } + }, "string.prototype.trimleft": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.0.tgz", @@ -16483,15 +16762,16 @@ } }, "terser-webpack-plugin": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-2.3.2.tgz", - "integrity": "sha512-SmvB/6gtEPv+CJ88MH5zDOsZdKXPS/Uzv2//e90+wM1IHFUhsguPKEILgzqrM1nQ4acRXN/SV4Obr55SXC+0oA==", + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-2.3.4.tgz", + "integrity": "sha512-Nv96Nws2R2nrFOpbzF6IxRDpIkkIfmhvOws+IqMvYdFLO7o6wAILWFKONFgaYy8+T4LVz77DQW0f7wOeDEAjrg==", "dev": true, "requires": { "cacache": "^13.0.1", "find-cache-dir": "^3.2.0", - "jest-worker": "^24.9.0", - "schema-utils": "^2.6.1", + "jest-worker": "^25.1.0", + "p-limit": "^2.2.2", + "schema-utils": "^2.6.4", "serialize-javascript": "^2.1.2", "source-map": "^0.6.1", "terser": "^4.4.3", @@ -17097,9 +17377,9 @@ "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==" }, "use-async-effect": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/use-async-effect/-/use-async-effect-2.2.1.tgz", - "integrity": "sha512-nPsDhym63uBWMRwWwcBclUWb3aPRYEwMIgXDylytQjM3r0BvzsqtRwuuxiNi5EBUa5bmo4Q206xr2LlmBWmd/g==" + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/use-async-effect/-/use-async-effect-2.2.2.tgz", + "integrity": "sha512-zFas5h1jm4FNCk+txUzfMUAQhIFJGdrhAmg2lSA+Q6BZKYFen/yZlHEx5RGWSUV1USjLFZ+yg989fFyzJHW3Zw==" }, "use-persisted-state": { "version": "0.3.0", @@ -17680,9 +17960,9 @@ } }, "webpack-dev-server": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.10.1.tgz", - "integrity": "sha512-AGG4+XrrXn4rbZUueyNrQgO4KGnol+0wm3MPdqGLmmA+NofZl3blZQKxZ9BND6RDNuvAK9OMYClhjOSnxpWRoA==", + "version": "3.10.3", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.10.3.tgz", + "integrity": "sha512-e4nWev8YzEVNdOMcNzNeCN947sWJNd43E5XvsJzbAL08kGc2frm1tQ32hTJslRS+H65LCb/AaUCYU7fjHCpDeQ==", "dev": true, "requires": { "ansi-html": "0.0.7", diff --git a/package.json b/package.json index 6e261d8fa..659570150 100644 --- a/package.json +++ b/package.json @@ -27,18 +27,18 @@ "react-router": "^5.1.2", "react-router-dom": "^5.1.2", "react-table": "=6.10.3", - "use-async-effect": "^2.2.1", + "use-async-effect": "^2.2.2", "use-persisted-state": "^0.3.0", "worker-loader": "^2.0.0", "zustand": "^2.2.2" }, "devDependencies": { - "@babel/cli": "^7.8.3", - "@babel/core": "^7.8.3", + "@babel/cli": "^7.8.4", + "@babel/core": "^7.8.4", "@babel/polyfill": "^7.8.3", - "@babel/preset-env": "^7.8.3", + "@babel/preset-env": "^7.8.4", "@babel/preset-react": "^7.8.3", - "@babel/runtime": "^7.8.3", + "@babel/runtime": "^7.8.4", "@babel/types": "^7.8.3", "autoprefixer": "^9.7.4", "babel-eslint": "^10.0.3", @@ -50,9 +50,9 @@ "cssnano": "^4.1.10", "eslint": "^6.8.0", "eslint-config-airbnb": "^18.0.1", - "eslint-plugin-import": "^2.20.0", + "eslint-plugin-import": "^2.20.1", "eslint-plugin-jsx-a11y": "^6.2.3", - "eslint-plugin-react": "^7.18.0", + "eslint-plugin-react": "^7.18.3", "file-loader": "^5.0.2", "html-loader": "^0.5.5", "html-webpack-inline-source-plugin": "0.0.10", @@ -61,15 +61,15 @@ "mini-css-extract-plugin": "^0.9.0", "node-sass": "^4.13.1", "optimize-css-assets-webpack-plugin": "^5.0.3", - "postcss-flexbugs-fixes": "^4.1.0", + "postcss-flexbugs-fixes": "^4.2.0", "postcss-loader": "^3.0.0", "sass-lint": "^1.13.1", "sass-loader": "^8.0.2", "style-loader": "^1.1.3", - "terser-webpack-plugin": "^2.3.2", + "terser-webpack-plugin": "^2.3.4", "webpack": "^4.41.5", "webpack-cli": "^3.3.10", - "webpack-dev-server": "^3.10.1", + "webpack-dev-server": "^3.10.3", "webpack-merge": "^4.2.2" } } From 3d95265ae002cc37008a174fe972667c061db611 Mon Sep 17 00:00:00 2001 From: deliciousmonster Date: Wed, 5 Feb 2020 13:55:24 -0700 Subject: [PATCH 05/19] handle errors from data queries clean up packages remove old harperdb provider --- package-lock.json | 174 ++---------------- package.json | 8 +- src/components/instance/browse/csvuploader.js | 7 +- src/components/instance/browse/datatable.js | 14 +- src/providers/harperdb.js | 63 ------- 5 files changed, 34 insertions(+), 232 deletions(-) delete mode 100644 src/providers/harperdb.js diff --git a/package-lock.json b/package-lock.json index 1cf578d79..9e6cb9626 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3315,6 +3315,14 @@ "@types/react": "*" } }, + "@types/react-table": { + "version": "6.8.6", + "resolved": "https://registry.npmjs.org/@types/react-table/-/react-table-6.8.6.tgz", + "integrity": "sha512-mCgyjD1KiMfZMHixOEzv7qXh5TSfmvgNbOb8qKLcTJbnGgsVSalcEdrNqASyaMp79MvdbMzxAcFQ3lftzmH7MA==", + "requires": { + "@types/react": "*" + } + }, "@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", @@ -4023,118 +4031,6 @@ "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==" }, - "autoprefixer": { - "version": "9.7.4", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.7.4.tgz", - "integrity": "sha512-g0Ya30YrMBAEZk60lp+qfX5YQllG+S5W3GYCFvyHTvhOki0AEQJLPEcIuGRsqVwLi8FvXPVtwTGhfr38hVpm0g==", - "dev": true, - "requires": { - "browserslist": "^4.8.3", - "caniuse-lite": "^1.0.30001020", - "chalk": "^2.4.2", - "normalize-range": "^0.1.2", - "num2fraction": "^1.2.2", - "postcss": "^7.0.26", - "postcss-value-parser": "^4.0.2" - }, - "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" - } - }, - "browserslist": { - "version": "4.8.5", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.8.5.tgz", - "integrity": "sha512-4LMHuicxkabIB+n9874jZX/az1IaZ5a+EUuvD7KFOu9x/Bd5YHyO0DIz2ls/Kl8g0ItS4X/ilEgf4T1Br0lgSg==", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30001022", - "electron-to-chromium": "^1.3.338", - "node-releases": "^1.1.46" - } - }, - "caniuse-lite": { - "version": "1.0.30001022", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001022.tgz", - "integrity": "sha512-FjwPPtt/I07KyLPkBQ0g7/XuZg6oUkYBVnPHNj3VHJbOjmmJ/GdSo/GUY6MwINEQvjhP6WZVbX8Tvms8xh0D5A==", - "dev": true - }, - "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" - } - }, - "electron-to-chromium": { - "version": "1.3.339", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.339.tgz", - "integrity": "sha512-C1i/vH6/kQx9YV8RddMkmW216GwW4pTrnYIlKmDFIqXA4fPwqDxIdGyHsuG+fgurHoljRz7/oaD+tztcryW/9g==", - "dev": true - }, - "node-releases": { - "version": "1.1.47", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.47.tgz", - "integrity": "sha512-k4xjVPx5FpwBUj0Gw7uvFOTF4Ep8Hok1I6qjwL3pLfwe7Y0REQSAqOwwv9TWBCUtMHxcXfY4PgRLRozcChvTcA==", - "dev": true, - "requires": { - "semver": "^6.3.0" - } - }, - "postcss": { - "version": "7.0.26", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.26.tgz", - "integrity": "sha512-IY4oRjpXWYshuTDFxMVkJDtWIk2LhsTlu8bZnbEJA4+bYT16Lvpo8Qv6EvDumhYRgzjZl489pmsY3qVgJQ08nA==", - "dev": true, - "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - }, - "dependencies": { - "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" - } - } - } - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "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 - }, - "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" - } - } - } - }, "aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", @@ -10903,12 +10799,6 @@ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true }, - "normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=", - "dev": true - }, "normalize-url": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-3.3.0.tgz", @@ -10943,12 +10833,6 @@ "boolbase": "~1.0.0" } }, - "num2fraction": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", - "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=", - "dev": true - }, "number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", @@ -14522,11 +14406,6 @@ "react-transition-group": "^4.3.0" } }, - "react-alert-template-basic": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/react-alert-template-basic/-/react-alert-template-basic-1.0.0.tgz", - "integrity": "sha512-6x5Us0oc+jj8BDNkvSWfQMESk5SdyGKitXdLb7CwIlIlecyATjCTKSWpLABg8tpKAPOSJu4Dv/fYUqxXEio/XA==" - }, "react-card-flip": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/react-card-flip/-/react-card-flip-1.0.10.tgz", @@ -14596,11 +14475,13 @@ } }, "react-table": { - "version": "6.10.3", - "resolved": "https://registry.npmjs.org/react-table/-/react-table-6.10.3.tgz", - "integrity": "sha512-sVlq2/rxVaQJywGD95+qGiMr/SMHFIFnXdx619BLOWE/Os5FOGtV6pQJNAjZixbQZiOu7dmBO1kME28uxh6wmA==", + "version": "6.11.5", + "resolved": "https://registry.npmjs.org/react-table/-/react-table-6.11.5.tgz", + "integrity": "sha512-LM+AS9v//7Y7lAlgTWW/cW6Sn5VOb3EsSkKQfQTzOW8FngB1FUskLLNEVkAYsTX9LjOWR3QlGjykJqCE6eXT/g==", "requires": { - "classnames": "^2.2.5" + "@types/react-table": "^6.8.5", + "classnames": "^2.2.5", + "react-is": "^16.8.1" } }, "react-transition-group": { @@ -16505,28 +16386,6 @@ "integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==", "dev": true }, - "style-loader": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-1.1.3.tgz", - "integrity": "sha512-rlkH7X/22yuwFYK357fMN/BxYOorfnfq0eD7+vqlemSK4wEcejFF1dg4zxP0euBW8NrYx2WZzZ8PPFevr7D+Kw==", - "dev": true, - "requires": { - "loader-utils": "^1.2.3", - "schema-utils": "^2.6.4" - }, - "dependencies": { - "schema-utils": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.4.tgz", - "integrity": "sha512-VNjcaUxVnEeun6B2fiiUDjXXBtD4ZSH7pdbfIu1pOFwgptDPLMo/z9jr4sUfsjFVPqDCEin/F7IYlq7/E6yDbQ==", - "dev": true, - "requires": { - "ajv": "^6.10.2", - "ajv-keywords": "^3.4.1" - } - } - } - }, "stylehacks": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-4.0.3.tgz", @@ -18477,11 +18336,6 @@ "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=" } } - }, - "zustand": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/zustand/-/zustand-2.2.2.tgz", - "integrity": "sha512-l51p5Ys4WcAqrds1pCd/lWQU5jzyayzyftyTmtwawhLLl/V9Wn9D0aX5Wu2fZ8XOco4C87UrurRVJ0Y/rNNrtg==" } } } diff --git a/package.json b/package.json index 659570150..47054e3ff 100644 --- a/package.json +++ b/package.json @@ -19,18 +19,16 @@ "json-stringify-pretty-compact": "^2.0.0", "react": "^16.12.0", "react-alert": "^6.0.0", - "react-alert-template-basic": "^1.0.0", "react-card-flip": "^1.0.10", "react-csv-reader": "^2.0.2", "react-dom": "^16.12.0", "react-json-editor-ajrm": "^2.5.9", "react-router": "^5.1.2", "react-router-dom": "^5.1.2", - "react-table": "=6.10.3", + "react-table": "^6.11.5", "use-async-effect": "^2.2.2", "use-persisted-state": "^0.3.0", - "worker-loader": "^2.0.0", - "zustand": "^2.2.2" + "worker-loader": "^2.0.0" }, "devDependencies": { "@babel/cli": "^7.8.4", @@ -40,7 +38,6 @@ "@babel/preset-react": "^7.8.3", "@babel/runtime": "^7.8.4", "@babel/types": "^7.8.3", - "autoprefixer": "^9.7.4", "babel-eslint": "^10.0.3", "babel-loader": "^8.0.6", "babel-plugin-transform-imports": "^2.0.0", @@ -65,7 +62,6 @@ "postcss-loader": "^3.0.0", "sass-lint": "^1.13.1", "sass-loader": "^8.0.2", - "style-loader": "^1.1.3", "terser-webpack-plugin": "^2.3.4", "webpack": "^4.41.5", "webpack-cli": "^3.3.10", diff --git a/src/components/instance/browse/csvuploader.js b/src/components/instance/browse/csvuploader.js index 1e13de266..989720d00 100644 --- a/src/components/instance/browse/csvuploader.js +++ b/src/components/instance/browse/csvuploader.js @@ -22,7 +22,12 @@ export default ({ refreshInstance, instance_id, auth }) => { // get the current record count of the table const getCurrentRecordCount = async () => { - const [{ newTotalRecords }] = await queryInstance({ operation: 'sql', sql: `SELECT count(*) as newTotalRecords FROM ${schema}.${table} ` }, auth); + let newTotalRecords = 0; + try { + [{ newTotalRecords }] = await queryInstance({ operation: 'sql', sql: `SELECT count(*) as newTotalRecords FROM ${schema}.${table} ` }, auth); + } catch (e) { + console.log('Failed to get row count'); + } return newTotalRecords; }; diff --git a/src/components/instance/browse/datatable.js b/src/components/instance/browse/datatable.js index 2b2fd7fb4..a06e7d1fe 100644 --- a/src/components/instance/browse/datatable.js +++ b/src/components/instance/browse/datatable.js @@ -29,6 +29,7 @@ export default ({ dataTableColumns, hashAttribute, onFilteredChange, filtered, o if (!sorted.length) return false; + let fetchError = false; let newTotalPages = 1; let newTotalRecords = 0; let newData = []; @@ -39,7 +40,8 @@ export default ({ dataTableColumns, hashAttribute, onFilteredChange, filtered, o [{ newTotalRecords }] = await queryInstance({ operation: 'sql', sql: countSQL }, auth); newTotalPages = newTotalRecords && Math.ceil(newTotalRecords / pageSize); } catch (e) { - // console.log('Failed to get row count'); + console.log('Failed to get row count'); + fetchError = true; } try { @@ -49,7 +51,15 @@ export default ({ dataTableColumns, hashAttribute, onFilteredChange, filtered, o dataSQL += ` LIMIT ${(page * pageSize) + pageSize} OFFSET ${page * pageSize}`; newData = await queryInstance({ operation: 'sql', sql: dataSQL }, auth); } catch (e) { - // console.log('Failed to get table data'); + console.log('Failed to get table data'); + fetchError = true; + } + + if (fetchError) { + setTableData([]); + setTotalPages(1); + setTotalRecords(0); + return setLoading(false); } if (newData) setTableData(newData); diff --git a/src/providers/harperdb.js b/src/providers/harperdb.js deleted file mode 100644 index a464839a2..000000000 --- a/src/providers/harperdb.js +++ /dev/null @@ -1,63 +0,0 @@ -import React, { useState } from 'react'; -import createPersistedState from 'use-persisted-state'; -import useAsyncEffect from 'use-async-effect'; - -import updateInstanceStates from '../util/updateInstanceStates'; -import getInstanceDataStructure from '../util/getInstanceDataStructure'; -import getInstanceClusterStatus from '../util/getInstanceClusterStatus'; - -const useInstanceState = createPersistedState('instances'); -export const HarperDBContext = React.createContext(); - -export const HarperDBProvider = ({ children }) => { - const [instances, setInstances] = useInstanceState([]); - const [structure, setStructure] = useState(false); - const [authError, setAuthError] = useState(false); - - const queryHarperDB = async (operation, authObject = false) => { - const { url, auth } = authObject || instances.find((i) => i.active); - const body = JSON.stringify(operation); - try { - const response = await fetch(url, { method: 'POST', body, headers: { 'Content-Type': 'application/json', authorization: `Basic ${auth}` } }); - return response.json(); - } catch { - return { error: 'Could not connect. Is HarperDB running?' }; - } - }; - - const login = async (authObject) => { - const dbResponse = await queryHarperDB({ operation: 'describe_all' }, authObject); - - if (dbResponse.error) { - setAuthError(dbResponse.error); - return false; - } - - setInstances(updateInstanceStates(instances, authObject)); - return setStructure(getInstanceDataStructure(dbResponse)); - }; - - const logout = () => { - setStructure(false); - setInstances(instances.map((i) => { i.active = false; return i; })); - }; - - const refreshNetwork = async () => setInstances(await getInstanceClusterStatus(instances, queryHarperDB)); - - const refreshInstance = async () => { - const dbResponse = await queryHarperDB({ operation: 'describe_all' }); - setStructure(getInstanceClusterStatus(dbResponse)); - }; - - useAsyncEffect(async () => { - const activeInstance = instances.find((i) => i.active); - if (activeInstance) login(activeInstance); - refreshNetwork(); - }, []); - - return ( - - {children} - - ); -}; From b7483fe937db4c3ac835ee4bd845d02cacb217ab Mon Sep 17 00:00:00 2001 From: deliciousmonster Date: Wed, 12 Feb 2020 11:28:28 -0700 Subject: [PATCH 06/19] reorg for clustering --- .eslintrc | 5 + mock_data/LMS_API.instances.json | 29 +- package-lock.json | 35 +- package.json | 6 +- src/app.scss | 20 +- src/assets/fonts/raleway-v14-latin-100.eot | Bin 0 -> 22858 bytes src/assets/fonts/raleway-v14-latin-100.svg | 346 +++++++++++++++++ src/assets/fonts/raleway-v14-latin-100.ttf | Bin 0 -> 53448 bytes src/assets/fonts/raleway-v14-latin-100.woff | Bin 0 -> 24260 bytes src/assets/fonts/raleway-v14-latin-100.woff2 | Bin 0 -> 19884 bytes src/assets/fonts/raleway-v14-latin-200.eot | Bin 0 -> 23429 bytes src/assets/fonts/raleway-v14-latin-200.svg | 347 ++++++++++++++++++ src/assets/fonts/raleway-v14-latin-200.ttf | Bin 0 -> 53516 bytes src/assets/fonts/raleway-v14-latin-200.woff | Bin 0 -> 24956 bytes src/assets/fonts/raleway-v14-latin-200.woff2 | Bin 0 -> 20452 bytes src/assets/styles/_button.scss | 17 +- src/assets/styles/_card.scss | 14 +- src/assets/styles/_clustering.scss | 35 ++ src/assets/styles/_forms.scss | 28 ++ src/assets/styles/_login.scss | 22 +- src/assets/styles/_react-table.scss | 21 +- src/assets/styles/_typography.scss | 50 +-- src/assets/styles/_vars.scss | 22 +- .../{instance => }/browse/csvuploader.js | 8 +- src/components/browse/datatable.js | 91 +++++ src/components/{instance => }/browse/index.js | 33 +- .../{instance => }/browse/jsonviewer.js | 2 +- src/components/clustering/index.js | 21 ++ src/components/clustering/manage.js | 101 +++++ src/components/clustering/manage_cardBack.js | 70 ++++ src/components/clustering/manage_cardFront.js | 82 +++++ .../clustering/manage_clusterCard.js | 27 ++ src/components/clustering/setup.js | 65 ++++ src/components/clustering/setup_enable.js | 31 ++ src/components/clustering/setup_port.js | 51 +++ src/components/clustering/setup_role.js | 21 ++ src/components/clustering/setup_user.js | 62 ++++ .../{instance/index.js => instance.js} | 10 +- src/components/instance/browse/datatable.js | 158 -------- .../instance/clustering/clusterCard.js | 26 -- src/components/instance/clustering/index.js | 77 ---- src/components/instances.js | 34 ++ src/components/instances/cardBack.js | 84 ++--- src/components/instances/cardFront.js | 4 +- src/components/instances/index.js | 25 -- src/components/instances/instanceCard.js | 1 + src/components/instances/newInstanceCard.js | 2 +- .../{instance => }/license/index.js | 0 src/components/login.js | 44 ++- src/components/{instance => }/roles/index.js | 0 src/components/{instance => }/routes.js | 4 +- .../{instance => }/shared/entityManager.js | 0 .../shared/entityManagerForm.js | 4 +- .../shared/entityManagerHeader.js | 0 .../{instance => }/shared/entityManagerRow.js | 2 +- src/components/topnav.js | 8 +- src/components/{instance => }/users/index.js | 0 src/util/createClusterUser.js | 14 + src/util/createClusterUserRole.js | 14 + src/util/defaultClusterFormData.js | 1 + ...FormData.js => defaultInstanceFormData.js} | 0 src/util/defaultTableState.js | 13 + src/util/enableInstanceClustering.js | 15 + src/util/getInstanceClusterStatus.js | 14 +- src/util/getTableData.js | 42 +++ src/util/queryInstance.js | 2 +- 66 files changed, 1766 insertions(+), 494 deletions(-) create mode 100644 src/assets/fonts/raleway-v14-latin-100.eot create mode 100644 src/assets/fonts/raleway-v14-latin-100.svg create mode 100644 src/assets/fonts/raleway-v14-latin-100.ttf create mode 100644 src/assets/fonts/raleway-v14-latin-100.woff create mode 100644 src/assets/fonts/raleway-v14-latin-100.woff2 create mode 100644 src/assets/fonts/raleway-v14-latin-200.eot create mode 100644 src/assets/fonts/raleway-v14-latin-200.svg create mode 100644 src/assets/fonts/raleway-v14-latin-200.ttf create mode 100644 src/assets/fonts/raleway-v14-latin-200.woff create mode 100644 src/assets/fonts/raleway-v14-latin-200.woff2 create mode 100644 src/assets/styles/_clustering.scss create mode 100644 src/assets/styles/_forms.scss rename src/components/{instance => }/browse/csvuploader.js (96%) create mode 100644 src/components/browse/datatable.js rename src/components/{instance => }/browse/index.js (78%) rename src/components/{instance => }/browse/jsonviewer.js (98%) create mode 100644 src/components/clustering/index.js create mode 100644 src/components/clustering/manage.js create mode 100644 src/components/clustering/manage_cardBack.js create mode 100644 src/components/clustering/manage_cardFront.js create mode 100644 src/components/clustering/manage_clusterCard.js create mode 100644 src/components/clustering/setup.js create mode 100644 src/components/clustering/setup_enable.js create mode 100644 src/components/clustering/setup_port.js create mode 100644 src/components/clustering/setup_role.js create mode 100644 src/components/clustering/setup_user.js rename src/components/{instance/index.js => instance.js} (83%) delete mode 100644 src/components/instance/browse/datatable.js delete mode 100644 src/components/instance/clustering/clusterCard.js delete mode 100644 src/components/instance/clustering/index.js create mode 100644 src/components/instances.js delete mode 100644 src/components/instances/index.js rename src/components/{instance => }/license/index.js (100%) rename src/components/{instance => }/roles/index.js (100%) rename src/components/{instance => }/routes.js (93%) rename src/components/{instance => }/shared/entityManager.js (100%) rename src/components/{instance => }/shared/entityManagerForm.js (96%) rename src/components/{instance => }/shared/entityManagerHeader.js (100%) rename src/components/{instance => }/shared/entityManagerRow.js (98%) rename src/components/{instance => }/users/index.js (100%) create mode 100644 src/util/createClusterUser.js create mode 100644 src/util/createClusterUserRole.js create mode 100644 src/util/defaultClusterFormData.js rename src/util/{defaultFormData.js => defaultInstanceFormData.js} (100%) create mode 100644 src/util/defaultTableState.js create mode 100644 src/util/enableInstanceClustering.js create mode 100644 src/util/getTableData.js diff --git a/.eslintrc b/.eslintrc index 71b24681d..cddb1d7dd 100644 --- a/.eslintrc +++ b/.eslintrc @@ -4,7 +4,12 @@ "env": { "browser": true }, + "plugins": [ + "react-hooks" + ], "rules": { + "react-hooks/rules-of-hooks": "error", + "react-hooks/exhaustive-deps": "warn", "camelcase": 0, "array-callback-return": 0, "react/require-extension": 0, diff --git a/mock_data/LMS_API.instances.json b/mock_data/LMS_API.instances.json index 455d3e3c0..cb42dae91 100644 --- a/mock_data/LMS_API.instances.json +++ b/mock_data/LMS_API.instances.json @@ -1,51 +1,66 @@ [ + { + "id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", + "name": "Local Instance", + "host": "localhost", + "port": 9925, + "node_name": "3f6c9e35f6a84fb7b09f8b3e7d4e20df", + "is_ssl": false + }, { "id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20de", "name": "Cluster Demo 1", - "url": "dev.harperdb.io", + "host": "dev.harperdb.io", "port": 9931, + "node_name": "ClusterDemoA1", "is_ssl": false }, { "id": "cee98941-8975-44d6-88e6-9a1a4fa02b25", "name": "Cluster Demo 2", - "url": "dev.harperdb.io", + "host": "dev.harperdb.io", "port": 9932, + "node_name": "ClusterDemoA2", "is_ssl": false }, { "id": "a32b2464-b5ca-4b9d-a10f-c6da23a7dcbe", "name": "Cluster Demo 3", - "url": "dev.harperdb.io", + "host": "dev.harperdb.io", "port": 9933, + "node_name": "ClusterDemoA3", "is_ssl": false }, { "id": "7a60b51d-7983-4659-a34b-cfb578a4db54", "name": "Cluster Demo 4", - "url": "dev.harperdb.io", + "host": "dev.harperdb.io", "port": 9934, + "node_name": "ClusterDemoA4", "is_ssl": false }, { "id": "f0d29404-9fb1-4d66-ab98-41f05e1ac665", "name": "Cluster Demo 5", - "url": "dev.harperdb.io", + "host": "dev.harperdb.io", "port": 9935, + "node_name": "ClusterDemoA5", "is_ssl": false }, { "id": "ccf2b958-0b0e-4bf6-a81d-6abee1e4eec5", "name": "Cluster Demo 6", - "url": "dev.harperdb.io", + "host": "dev.harperdb.io", "port": 9936, + "node_name": "ClusterDemoA6", "is_ssl": false }, { "id": "8291c224-6911-4155-b5a9-34559e5a45ff", "name": "Cluster Demo 7", - "url": "dev.harperdb.io", + "host": "dev.harperdb.io", "port": 9937, + "node_name": "ClusterDemoA7", "is_ssl": false } ] diff --git a/package-lock.json b/package-lock.json index 9e6cb9626..aae25af82 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6955,6 +6955,12 @@ } } }, + "eslint-plugin-react-hooks": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-2.3.0.tgz", + "integrity": "sha512-gLKCa52G4ee7uXzdLiorca7JIQZPPXRAQDXV83J4bUEeUuc5pIEyZYAZ45Xnxe5IuupxEqHS+hUhSLIimK1EMw==", + "dev": true + }, "eslint-scope": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.0.0.tgz", @@ -11227,9 +11233,9 @@ "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=" }, "pako": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.10.tgz", - "integrity": "sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw==", + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", "dev": true }, "papaparse": { @@ -17240,6 +17246,11 @@ "resolved": "https://registry.npmjs.org/use-async-effect/-/use-async-effect-2.2.2.tgz", "integrity": "sha512-zFas5h1jm4FNCk+txUzfMUAQhIFJGdrhAmg2lSA+Q6BZKYFen/yZlHEx5RGWSUV1USjLFZ+yg989fFyzJHW3Zw==" }, + "use-interval": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/use-interval/-/use-interval-1.2.1.tgz", + "integrity": "sha512-qKe8IigaK0WmwwgWB6yLEmb7olb0rY31+anvMk0QMO+UEHeHG3ABucuXMrjqc8F0NsTLHNFVL8BNo20x50FRwA==" + }, "use-persisted-state": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/use-persisted-state/-/use-persisted-state-0.3.0.tgz", @@ -17380,9 +17391,9 @@ } }, "webpack": { - "version": "4.41.5", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.41.5.tgz", - "integrity": "sha512-wp0Co4vpyumnp3KlkmpM5LWuzvZYayDwM2n17EHFr4qxBBbRokC7DJawPJC7TfSFZ9HZ6GsdH40EBj4UV0nmpw==", + "version": "4.41.6", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.41.6.tgz", + "integrity": "sha512-yxXfV0Zv9WMGRD+QexkZzmGIh54bsvEs+9aRWxnN8erLWEOehAKUTeNBoUbA6HPEZPlRo7KDi2ZcNveoZgK9MA==", "dev": true, "requires": { "@webassemblyjs/ast": "1.8.5", @@ -17452,9 +17463,9 @@ } }, "webpack-cli": { - "version": "3.3.10", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.3.10.tgz", - "integrity": "sha512-u1dgND9+MXaEt74sJR4PR7qkPxXUSQ0RXYq8x1L6Jg1MYVEmGPrH6Ah6C4arD4r0J1P5HKjRqpab36k0eIzPqg==", + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.3.11.tgz", + "integrity": "sha512-dXlfuml7xvAFwYUPsrtQAA9e4DOe58gnzSxhgrO/ZM/gyXTBowrsYeubyN4mqGhYdpXMFNyQ6emjJS9M7OBd4g==", "dev": true, "requires": { "chalk": "2.4.2", @@ -17673,9 +17684,9 @@ } }, "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==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", + "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", "dev": true, "requires": { "p-try": "^2.0.0" diff --git a/package.json b/package.json index 47054e3ff..1d423557c 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "react-router-dom": "^5.1.2", "react-table": "^6.11.5", "use-async-effect": "^2.2.2", + "use-interval": "^1.2.1", "use-persisted-state": "^0.3.0", "worker-loader": "^2.0.0" }, @@ -50,6 +51,7 @@ "eslint-plugin-import": "^2.20.1", "eslint-plugin-jsx-a11y": "^6.2.3", "eslint-plugin-react": "^7.18.3", + "eslint-plugin-react-hooks": "^2.3.0", "file-loader": "^5.0.2", "html-loader": "^0.5.5", "html-webpack-inline-source-plugin": "0.0.10", @@ -63,8 +65,8 @@ "sass-lint": "^1.13.1", "sass-loader": "^8.0.2", "terser-webpack-plugin": "^2.3.4", - "webpack": "^4.41.5", - "webpack-cli": "^3.3.10", + "webpack": "^4.41.6", + "webpack-cli": "^3.3.11", "webpack-dev-server": "^3.10.3", "webpack-merge": "^4.2.2" } diff --git a/src/app.scss b/src/app.scss index eaf823b90..b87c5ed35 100644 --- a/src/app.scss +++ b/src/app.scss @@ -1,18 +1,28 @@ +// external resources @import '~@nio/ui-kit/dist/nio'; @import '~react-table/react-table'; + +// core styles @import './assets/styles/vars'; -@import '~font-awesome/scss/font-awesome'; @import './assets/styles/base'; +@import './assets/styles/typography'; + +// global styles +@import './assets/styles/forms'; @import './assets/styles/alert'; @import './assets/styles/divider'; -@import './assets/styles/typography'; -@import './assets/styles/react-table'; @import './assets/styles/card'; -@import './assets/styles/db-browser'; -@import './assets/styles/login'; @import './assets/styles/button'; @import './assets/styles/dropdown'; @import './assets/styles/code'; + +// component styles @import './assets/styles/topnav'; +@import './assets/styles/db-browser'; +@import './assets/styles/react-table'; +@import './assets/styles/login'; @import './assets/styles/csv-input'; +@import './assets/styles/clustering'; + +@import '~font-awesome/scss/font-awesome'; diff --git a/src/assets/fonts/raleway-v14-latin-100.eot b/src/assets/fonts/raleway-v14-latin-100.eot new file mode 100644 index 0000000000000000000000000000000000000000..375c9d8133f7edc7621a7c5277c6677e96fc6f57 GIT binary patch literal 22858 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/fonts/raleway-v14-latin-100.ttf b/src/assets/fonts/raleway-v14-latin-100.ttf new file mode 100644 index 0000000000000000000000000000000000000000..c8eaba2008a4e9aa1a2d2745d7a422122f0bcaa4 GIT binary patch literal 53448 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 literal 0 HcmV?d00001 diff --git a/src/assets/fonts/raleway-v14-latin-100.woff b/src/assets/fonts/raleway-v14-latin-100.woff new file mode 100644 index 0000000000000000000000000000000000000000..fecf72e5d9e5f47e9ba46b84813354f4a6bfce7d GIT binary patch literal 24260 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 literal 0 HcmV?d00001 diff --git a/src/assets/fonts/raleway-v14-latin-100.woff2 b/src/assets/fonts/raleway-v14-latin-100.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..dbeefad483b7939081da6c076821ef2c4df12107 GIT binary patch literal 19884 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 JW1o~Ktp;*brp*8V literal 0 HcmV?d00001 diff --git a/src/assets/fonts/raleway-v14-latin-200.svg b/src/assets/fonts/raleway-v14-latin-200.svg new file mode 100644 index 000000000..958f46b76 --- /dev/null +++ b/src/assets/fonts/raleway-v14-latin-200.svg @@ -0,0 +1,347 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/assets/fonts/raleway-v14-latin-200.ttf b/src/assets/fonts/raleway-v14-latin-200.ttf new file mode 100644 index 0000000000000000000000000000000000000000..88e34739f314a91d7aee4e0d6fa2a76f0c66fbdf GIT binary patch literal 53516 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 bVHl&O#@t7mkv)tZN#ave#DfExC{O_maQ literal 0 HcmV?d00001 diff --git a/src/assets/fonts/raleway-v14-latin-200.woff b/src/assets/fonts/raleway-v14-latin-200.woff new file mode 100644 index 0000000000000000000000000000000000000000..d1bbeaacadaed0063e8d976f1ccc3a20a841506e GIT binary patch literal 24956 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 literal 0 HcmV?d00001 diff --git a/src/assets/fonts/raleway-v14-latin-200.woff2 b/src/assets/fonts/raleway-v14-latin-200.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..e13afef7dae6931793d53316ce5264f8d83a689b GIT binary patch literal 20452 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 { return setTimeout(() => validateData(), 1000); } - refreshInstance(); + refreshInstance(Date.now()); return setTimeout(() => { setStatus(false); diff --git a/src/components/browse/datatable.js b/src/components/browse/datatable.js new file mode 100644 index 000000000..262cbb93f --- /dev/null +++ b/src/components/browse/datatable.js @@ -0,0 +1,91 @@ +import React, { useEffect, useState } from 'react'; +import ReactTable from 'react-table'; +import { useHistory, useParams } from 'react-router'; +import useAsyncEffect from 'use-async-effect'; +import useInterval from 'use-interval'; +import { Card, CardBody, Col, Row } from '@nio/ui-kit'; + +import commaNumbers from '../../util/commaNumbers'; +import getTableData from '../../util/getTableData'; +import defaultTableState from '../../util/defaultTableState'; + +const dataRefreshInterval = 3000; +let tableChangeTimeout = false; + +export default ({ activeTable: { hashAttribute, dataTableColumns }, auth, instance_id, refreshInstance, structure }) => { + const history = useHistory(); + const { schema, table } = useParams(); + const [tableState, setTableState] = useState(defaultTableState); + + useAsyncEffect( + async () => { + clearTimeout(tableChangeTimeout); + tableChangeTimeout = setTimeout(async () => { + setTableState({ ...tableState, loading: true }); + const newData = await getTableData({ schema, table, tableState, auth }); + setTableState({ ...tableState, tableData: newData.tableData, totalPages: newData.totalPages, totalRecords: newData.totalRecords, loading: false }); + }, 100); + }, + () => { + clearTimeout(tableChangeTimeout); + }, + [table, structure, tableState.sorted, tableState.page, tableState.filtered, tableState.pageSize, tableState.lastUpdate], + ); + + useEffect(() => { + if (table) { + setTableState({ ...tableState, filtered: [], sorted: [{ id: hashAttribute, desc: false }], page: 0 }); + } + }, [table]); + + useInterval(() => { + if (tableState.autoRefresh) { + setTableState({ ...tableState, lastUpdate: Date.now() }); + } + }, dataRefreshInterval); + + return ( + <> + + + + {schema}  + {table && `> ${table} > `} + {commaNumbers(tableState.totalRecords)} record{tableState.totalRecords !== 1 ? 's' : ''} + + + + refreshInstance(Date.now())} /> + auto + setTableState({ ...tableState, autoRefresh: !tableState.autoRefresh, lastUpdate: Date.now() })} /> + | + setTableState({ ...tableState, filtered: tableState.showFilter ? [] : tableState.filtered, showFilter: !tableState.showFilter })} /> + history.push(`/instances/${instance_id}/browse/${schema}/${table}/add`)} /> + history.push(`/instances/${instance_id}/browse/${schema}/${table}/csv`)} /> + + + + + setTableState({ ...tableState, filtered: value })} + filtered={tableState.filtered} + onSortedChange={(value) => setTableState({ ...tableState, sorted: value })} + sorted={tableState.sorted} + onPageChange={(value) => setTableState({ ...tableState, page: value })} + page={tableState.page} + filterable={tableState.showFilter} + defaultPageSize={tableState.pageSize} + pageSize={tableState.pageSize} + onPageSizeChange={(value) => setTableState({ ...tableState, pageSize: value })} + getTrProps={(state, rowInfo) => ({ onClick: () => history.push(`/instances/${instance_id}/browse/${schema}/${table}/edit/${rowInfo.original[hashAttribute]}`) })} + /> + + + + ); +}; diff --git a/src/components/instance/browse/index.js b/src/components/browse/index.js similarity index 78% rename from src/components/instance/browse/index.js rename to src/components/browse/index.js index 180a92d83..762713edf 100644 --- a/src/components/instance/browse/index.js +++ b/src/components/browse/index.js @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect } from 'react'; import { Row, Col, CardBody, Card } from '@nio/ui-kit'; import { useHistory } from 'react-router'; import { useParams } from 'react-router-dom'; @@ -12,22 +12,10 @@ export default ({ auth, structure, refreshInstance }) => { const history = useHistory(); const { instance_id, schema, table, action } = useParams(); - const [filtered, onFilteredChange] = useState([]); - const [sorted, onSortedChange] = useState([]); - const [page, onPageChange] = useState(0); - - const schemas = structure && Object.keys(structure); - const tables = structure && schemas && structure[schema] && Object.keys(structure[schema]); + const schemas = structure && Object.keys(structure).sort(); + const tables = structure && schemas && structure[schema] && Object.keys(structure[schema]).sort(); const activeTable = structure && schemas && structure[schema] && tables && structure[schema][table] && structure[schema][table]; - useEffect(() => { - if (activeTable) { - onFilteredChange([]); - onSortedChange([{ id: activeTable.hashAttribute, desc: false }]); - onPageChange(0); - } - }, [activeTable]); - useEffect(() => { switch (true) { case (!schemas && history.location.pathname !== '/browse'): @@ -44,9 +32,9 @@ export default ({ auth, structure, refreshInstance }) => { default: break; } - }, [schema, schemas, table, tables]); + }, [schema, table, schemas, tables, history, instance_id]); - return ( + return structure ? ( { /> ) : schema && table && activeTable ? ( { )} + ) : ( + ); }; diff --git a/src/components/instance/browse/jsonviewer.js b/src/components/browse/jsonviewer.js similarity index 98% rename from src/components/instance/browse/jsonviewer.js rename to src/components/browse/jsonviewer.js index 9e08b1261..69c94093d 100644 --- a/src/components/instance/browse/jsonviewer.js +++ b/src/components/browse/jsonviewer.js @@ -5,7 +5,7 @@ import { Button, Card, CardBody, Col, Form, Row } from '@nio/ui-kit'; import { useHistory, useParams } from 'react-router'; import useAsyncEffect from 'use-async-effect'; -import queryInstance from '../../../util/queryInstance'; +import queryInstance from '../../util/queryInstance'; export default ({ newEntityColumns, hashAttribute, instance_id, refreshInstance, auth }) => { const history = useHistory(); diff --git a/src/components/clustering/index.js b/src/components/clustering/index.js new file mode 100644 index 000000000..21b0d3a9e --- /dev/null +++ b/src/components/clustering/index.js @@ -0,0 +1,21 @@ +import React from 'react'; + +import Setup from './setup'; +import Manage from './manage'; + +export default ({ auth, structure, network, refreshInstance }) => (!network ? ( + +) : network.is_enabled && network.cluster_user && network.cluster_role ? ( + +) : ( + +)); diff --git a/src/components/clustering/manage.js b/src/components/clustering/manage.js new file mode 100644 index 000000000..9d55c85dd --- /dev/null +++ b/src/components/clustering/manage.js @@ -0,0 +1,101 @@ +import React, { useState } from 'react'; +import { Code, Row, Col } from '@nio/ui-kit'; +import { useParams } from 'react-router-dom'; +import stringify from 'json-stringify-pretty-compact'; +import { useHistory } from 'react-router'; +import useAsyncEffect from 'use-async-effect'; + +import EntityManager from '../shared/entityManager'; +import useLMS from '../../stores/lmsData'; +import ClusterCard from './manage_clusterCard'; +import useInstanceAuth from '../../stores/instanceAuths'; + +export default ({ auth, network, refreshInstance, structure }) => { + const history = useHistory(); + const [lmsData] = useLMS({ auth: false, instances: [] }); + const { instance_id, schema, table } = useParams(); + const [instanceAuths, setInstanceAuths] = useInstanceAuth({}); + const [entities, setEntities] = useState({ schemas: [], tables: [] }); + + useAsyncEffect(() => { + if (structure) { + const newSchemas = structure && Object.keys(structure); + const newTables = structure && newSchemas && structure[schema] && Object.keys(structure[schema]); + setEntities({ schemas: newSchemas, tables: newTables }); + } + }, [structure, schema]); + + useAsyncEffect(() => { + switch (true) { + case (!entities.schemas && history.location.pathname !== '/clustering'): + history.push(`/instances/${instance_id}/clustering`); + break; + case (entities.schemas && entities.schemas.length && !schema): + case (entities.schemas && entities.schemas.length && schema && !entities.schemas.includes(schema)): + history.push(`/instances/${instance_id}/clustering/${entities.schemas[0]}`); + break; + case (entities.tables && entities.tables.length && !table): + case (entities.tables && entities.tables.length && table && !entities.tables.includes(table)): + history.push(`/instances/${instance_id}/clustering/${schema}/${entities.tables[0]}`); + break; + default: + break; + } + }, [schema, entities, table]); + + return ( + + + + { schema && ( + + )} + + +

+ {schema} > {table} > data replication +
+ + {lmsData.instances.filter((i) => i.id !== instance_id).map((i) => { + const connection = network && network.outbound_connections.find((n) => n.node_name === i.node_name); + const pub = schema && table && connection && connection.subscriptions && connection.subscriptions.find((s) => s.channel === `${schema}:${table}` && s.publish); + const sub = schema && table && connection && connection.subscriptions && connection.subscriptions.find((s) => s.channel === `${schema}:${table}` && s.subscribe); + return ( + setInstanceAuths({ ...instanceAuths, [id]: { user, pass } })} + /> + ); + })} + +
+ + {stringify(network.outbound_connections, { maxLength: 20 })} + +
+ + + ); +}; diff --git a/src/components/clustering/manage_cardBack.js b/src/components/clustering/manage_cardBack.js new file mode 100644 index 000000000..00af2a965 --- /dev/null +++ b/src/components/clustering/manage_cardBack.js @@ -0,0 +1,70 @@ +import React, { useState } from 'react'; +import { Button, Card, CardBody, Input, Row, Col } from '@nio/ui-kit'; +import useAsyncEffect from 'use-async-effect'; +import { useAlert } from 'react-alert'; +import queryInstance from '../../util/queryInstance'; +import defaultFormData from '../../util/defaultInstanceFormData'; + +export default ({ id, host, port, is_ssl, setAuth, flipCard, flipState }) => { + const alert = useAlert(); + const [formData, updateForm] = useState(defaultFormData); + + useAsyncEffect(async () => { + if (formData.submitted) { + const instance = await queryInstance({ operation: 'describe_all' }, { ...formData, host, port, is_ssl }); + if (instance.error) { + alert.error(instance.error); + updateForm({ ...formData, error: instance.error, submitted: false }); + } else { + updateForm(defaultFormData); + setAuth({ id, user: formData.user, pass: formData.pass }); + flipCard(); + } + } + }, [formData]); + + return ( + + {flipState && ( + + updateForm({ ...formData, user: e.target.value, error: false })} + className="text-center mb-1" + type="text" + title="username" + placeholder="user" + /> + updateForm({ ...formData, pass: e.target.value, error: false })} + className="text-center mb-2" + type="password" + title="password" + placeholder="pass" + /> + + + + + + + + + + )} + + ); +}; diff --git a/src/components/clustering/manage_cardFront.js b/src/components/clustering/manage_cardFront.js new file mode 100644 index 000000000..28c6580a3 --- /dev/null +++ b/src/components/clustering/manage_cardFront.js @@ -0,0 +1,82 @@ +import React, { useState } from 'react'; +import { Button, Card, CardBody, Col, Row } from '@nio/ui-kit'; +import useAsyncEffect from 'use-async-effect'; + +import queryInstance from '../../util/queryInstance'; + +export default ({ id, host, port, name, node_name, is_ssl, connection, pub, sub, hasAuth, flipCard, refreshInstance, auth }) => { + const [buttonState, setButtonState] = useState(false); + + useAsyncEffect(async () => { + if (buttonState) { + if (buttonState === 'connect') { + const response = await queryInstance({ operation: 'add_node', name: id, host, port }, auth); + console.log(response); + refreshInstance(Date.now()); + setButtonState(false); + } + if (buttonState === 'pub') { + const response = await queryInstance({ operation: 'update_node', name: id, host, port }, auth); + console.log(response); + refreshInstance(Date.now()); + setButtonState(false); + } + if (buttonState === 'sub') { + const response = await queryInstance({ operation: 'update_node', name: id, host, port }, auth); + console.log(response); + refreshInstance(Date.now()); + setButtonState(false); + } + } + }, [buttonState]); + + return ( + + + + + {name} + + + {hasAuth ? ( + + ) : ( + + )} + + +
+ + URL + http{is_ssl && 's'}://{host}:{port} +
+ Node Name + {node_name} +
+
+ {!hasAuth ? ( + + + + + + ) : !connection ? ( + + + + + + ) : ( + + + + + + + + + )} +
+
+ ); +}; diff --git a/src/components/clustering/manage_clusterCard.js b/src/components/clustering/manage_clusterCard.js new file mode 100644 index 000000000..c0303201e --- /dev/null +++ b/src/components/clustering/manage_clusterCard.js @@ -0,0 +1,27 @@ +import React, { useState } from 'react'; +import ReactCardFlip from 'react-card-flip'; +import { Col } from '@nio/ui-kit'; + +import CardFront from './manage_cardFront'; +import CardBack from './manage_cardBack'; + +export default (props) => { + const [flipState, setFlipState] = useState(false); + + return ( + + + setFlipState(true)} + {...props} + /> + + setFlipState(false)} + flipState={flipState} + {...props} + /> + + + ); +}; diff --git a/src/components/clustering/setup.js b/src/components/clustering/setup.js new file mode 100644 index 000000000..7e4362561 --- /dev/null +++ b/src/components/clustering/setup.js @@ -0,0 +1,65 @@ +import React, { useState } from 'react'; +import { Row, Col, Card, CardBody } from '@nio/ui-kit'; +import { useParams } from 'react-router-dom'; + +import Role from './setup_role'; +import User from './setup_user'; +import Port from './setup_port'; +import Enable from './setup_enable'; + +export default ({ auth, network, refreshInstance }) => { + const [port, setPort] = useState(false); + const { instance_id } = useParams(); + + return ( + + + clustering + + + HarperDB allows you to replicate data between one or more instances. We call this feature "clustering". +
+ This instance does not yet have clustering enabled. To enable clustering, follow the steps outlined at right. +
+ The Cluster User and Password must be the same between all instances in a HarperDB cluster. +
+
+ + + follow the steps below to enable clustering for this instance. + + + + {network.cluster_role && ( + + )} + {network.cluster_user && ( + + )} + {port && ( + + )} + + + +
+ ); +}; diff --git a/src/components/clustering/setup_enable.js b/src/components/clustering/setup_enable.js new file mode 100644 index 000000000..06b6e8093 --- /dev/null +++ b/src/components/clustering/setup_enable.js @@ -0,0 +1,31 @@ +import React, { useState } from 'react'; +import { Row, Col, Button } from '@nio/ui-kit'; +import useAsyncEffect from 'use-async-effect'; + +import enableInstanceClustering from '../../util/enableInstanceClustering'; + +export default ({ port, username, instanceId, auth, refreshInstance }) => { + const [submitted, setSubmitted] = useState(false); + + useAsyncEffect(async () => { + if (submitted) { + const result = await enableInstanceClustering({ port, username, instanceId, auth, refreshInstance }); + console.log(result); + } + }, [submitted]); + + return ( + + + + + + ); +}; diff --git a/src/components/clustering/setup_port.js b/src/components/clustering/setup_port.js new file mode 100644 index 000000000..fc6656306 --- /dev/null +++ b/src/components/clustering/setup_port.js @@ -0,0 +1,51 @@ +import React, { useState } from 'react'; +import { Row, Col, Button, Input } from '@nio/ui-kit'; +import useAsyncEffect from 'use-async-effect'; +import { useAlert } from 'react-alert'; + +import defaultFormData from '../../util/defaultClusterFormData'; + +export default ({ port, setPort }) => { + const alert = useAlert(); + const [portFormData, updatePortForm] = useState({ ...defaultFormData }); + + useAsyncEffect(async () => { + const { submitted, newPort } = portFormData; + if (submitted) { + if (!newPort) { + updatePortForm({ ...portFormData, error: true, submitted: false }); + alert.error('All fields are required.'); + } else { + setPort(newPort); + updatePortForm({ ...defaultFormData }); + } + } + }, [portFormData]); + + return port ? ( + + Cluster Port + {port} + + + + + ) : ( + + Choose a Cluster Port + + updatePortForm({ ...portFormData, newPort: e.target.value, error: false })} + className="mb-1" + type="number" + title="port" + placeholder="cluster port" + /> + + + + + + + ); +}; diff --git a/src/components/clustering/setup_role.js b/src/components/clustering/setup_role.js new file mode 100644 index 000000000..a85f08e7c --- /dev/null +++ b/src/components/clustering/setup_role.js @@ -0,0 +1,21 @@ +import React from 'react'; +import { Row, Col, Button } from '@nio/ui-kit'; + +import createClusterUserRole from '../../util/createClusterUserRole'; + +export default ({ auth, clusterRole, refreshInstance }) => (clusterRole ? ( + + Cluster Role + {clusterRole} + + + + +) : ( + + First, create a Cluster Role + + + + +)); diff --git a/src/components/clustering/setup_user.js b/src/components/clustering/setup_user.js new file mode 100644 index 000000000..123c11092 --- /dev/null +++ b/src/components/clustering/setup_user.js @@ -0,0 +1,62 @@ +import React, { useState } from 'react'; +import { Row, Col, Button, Input } from '@nio/ui-kit'; +import useAsyncEffect from 'use-async-effect'; +import { useAlert } from 'react-alert'; + +import defaultFormData from '../../util/defaultClusterFormData'; +import createClusterUser from '../../util/createClusterUser'; + +export default ({ clusterUser, clusterRole, auth, refreshInstance }) => { + const alert = useAlert(); + const [userFormData, updateUserForm] = useState({ ...defaultFormData }); + + useAsyncEffect(async () => { + const { submitted, username, password } = userFormData; + if (submitted) { + if (!username || !password) { + updateUserForm({ ...userFormData, error: true, submitted: false }); + alert.error('All fields are required.'); + } else { + const result = await createClusterUser({ username, password, role: clusterRole, auth, refreshInstance }); + if (result.message.indexOf('successfully') === -1) { + alert.error(result.message); + } + } + } + }, [userFormData]); + + return clusterUser ? ( + + Cluster User + {clusterUser} + + + + + ) : ( + + Create a Cluster User + + updateUserForm({ ...userFormData, username: e.target.value, error: false })} + className="mb-1" + type="text" + title="username" + placeholder="username" + /> + + + updateUserForm({ ...userFormData, password: e.target.value, error: false })} + className="mb-1" + type="password" + title="password" + placeholder="password" + /> + + + + + + ); +}; diff --git a/src/components/instance/index.js b/src/components/instance.js similarity index 83% rename from src/components/instance/index.js rename to src/components/instance.js index 7958aac15..b62905f5c 100644 --- a/src/components/instance/index.js +++ b/src/components/instance.js @@ -2,11 +2,11 @@ import React, { useState } from 'react'; import { Route, Switch, useParams } from 'react-router-dom'; import useAsyncEffect from 'use-async-effect'; -import TopNav from '../topnav'; +import TopNav from './topnav'; import routes from './routes'; -import useLMS from '../../stores/lmsData'; -import useInstanceAuth from '../../stores/instanceAuths'; -import buildActiveInstanceObject from '../../util/buildActiveInstanceObject'; +import useLMS from '../stores/lmsData'; +import useInstanceAuth from '../stores/instanceAuths'; +import buildActiveInstanceObject from '../util/buildActiveInstanceObject'; export default () => { const { instance_id } = useParams(); @@ -26,7 +26,7 @@ export default () => { return ( <> - + {routes.map((route) => { const ThisRouteComponent = route.component; diff --git a/src/components/instance/browse/datatable.js b/src/components/instance/browse/datatable.js deleted file mode 100644 index a06e7d1fe..000000000 --- a/src/components/instance/browse/datatable.js +++ /dev/null @@ -1,158 +0,0 @@ -import React, { useState } from 'react'; -import ReactTable from 'react-table'; -import { useHistory, useParams } from 'react-router'; -import useAsyncEffect from 'use-async-effect'; -import { Card, CardBody, Col, Row } from '@nio/ui-kit'; - -import commaNumbers from '../../../util/commaNumbers'; -import queryInstance from '../../../util/queryInstance'; - -export default ({ dataTableColumns, hashAttribute, onFilteredChange, filtered, onSortedChange, sorted, onPageChange, page, auth, instance_id, refreshInstance, structure }) => { - const history = useHistory(); - const { schema, table } = useParams(); - - const [tableData, setTableData] = useState([]); - const [pages, setTotalPages] = useState(-1); - const [loading, setLoading] = useState(true); - const [pageSize, onPageSizeChange] = useState(20); - const [totalRecords, setTotalRecords] = useState(0); - const [showFilter, toggleShowFilter] = useState(false); - const [autoRefresh, toggleAutoRefresh] = useState(false); - const [lastUpdate, setLastUpdate] = useState(); - - const dataRefreshInterval = 3000; - let dataRefreshTimeout = false; - let tableChangeTimeout = false; - - const loadNewData = async (showSpinner = true) => { - setLoading(showSpinner); - - if (!sorted.length) return false; - - let fetchError = false; - let newTotalPages = 1; - let newTotalRecords = 0; - let newData = []; - - try { - let countSQL = `SELECT count(*) as newTotalRecords FROM ${schema}.${table} `; - if (filtered.length) countSQL += `WHERE ${filtered.map((f) => ` \`${f.id}\` LIKE '%${f.value}%'`).join(' AND ')} `; - [{ newTotalRecords }] = await queryInstance({ operation: 'sql', sql: countSQL }, auth); - newTotalPages = newTotalRecords && Math.ceil(newTotalRecords / pageSize); - } catch (e) { - console.log('Failed to get row count'); - fetchError = true; - } - - try { - let dataSQL = `SELECT * FROM ${schema}.${table} `; - if (filtered.length) dataSQL += `WHERE ${filtered.map((f) => ` \`${f.id}\` LIKE '%${f.value}%'`).join(' AND ')} `; - if (sorted.length) dataSQL += `ORDER BY \`${sorted[0].id}\` ${sorted[0].desc ? 'DESC' : 'ASC'}`; - dataSQL += ` LIMIT ${(page * pageSize) + pageSize} OFFSET ${page * pageSize}`; - newData = await queryInstance({ operation: 'sql', sql: dataSQL }, auth); - } catch (e) { - console.log('Failed to get table data'); - fetchError = true; - } - - if (fetchError) { - setTableData([]); - setTotalPages(1); - setTotalRecords(0); - return setLoading(false); - } - - if (newData) setTableData(newData); - if (newTotalPages) setTotalPages(newTotalPages); - setTotalRecords(newTotalRecords); - return setLoading(false); - }; - - const handleRefreshClick = () => refreshInstance(Date.now()); - - const handleNewRecordClick = () => history.push(`/instances/${instance_id}/browse/${schema}/${table}/add`); - - const handleCSVUploadClick = () => history.push(`/instances/${instance_id}/browse/${schema}/${table}/csv`); - - const handleRowClick = (newActiveRecord) => history.push(`/instances/${instance_id}/browse/${schema}/${table}/edit/${newActiveRecord[hashAttribute]}`); - - const handleFilterClick = () => { if (showFilter) onFilteredChange([]); toggleShowFilter(!showFilter); }; - - useAsyncEffect( - () => { - clearTimeout(tableChangeTimeout); - tableChangeTimeout = setTimeout(() => loadNewData(), 0); - }, - () => { - clearTimeout(tableChangeTimeout); - }, - [sorted, table, pageSize, page, filtered, structure], - ); - - useAsyncEffect( - async () => { - if (autoRefresh) { - clearTimeout(dataRefreshTimeout); - const [{ newTotalRecords }] = await queryInstance({ - operation: 'sql', - sql: `SELECT count(*) as newTotalRecords FROM ${schema}.${table} `, - }, auth); - if (newTotalRecords !== totalRecords) { - loadNewData(false); - setTimeout(() => refreshInstance(Date.now()), 100); - } - dataRefreshTimeout = setTimeout(() => setLastUpdate(Date.now()), dataRefreshInterval); - } - }, - () => clearTimeout(dataRefreshTimeout), - [lastUpdate, autoRefresh], - ); - - return ( - <> - - - - {schema}  - {table && `> ${table} > `} - {loading ? ( - - ) : ( - {commaNumbers(totalRecords)} record{totalRecords !== 1 ? 's' : ''} - )} - - - - - auto - toggleAutoRefresh(!autoRefresh)} /> - | - - - - - - - - ({ onClick: () => handleRowClick(rowInfo.original) })} - /> - - - - ); -}; diff --git a/src/components/instance/clustering/clusterCard.js b/src/components/instance/clustering/clusterCard.js deleted file mode 100644 index f77013f77..000000000 --- a/src/components/instance/clustering/clusterCard.js +++ /dev/null @@ -1,26 +0,0 @@ -import React from 'react'; -import { Button, Card, CardBody, Col, Row } from '@nio/ui-kit'; -import { useAlert } from 'react-alert'; - -export default ({ id, name }) => { - const alert = useAlert(); - - return ( - - - - {name} -
- - - - - - - - -
-
- - ); -}; diff --git a/src/components/instance/clustering/index.js b/src/components/instance/clustering/index.js deleted file mode 100644 index ccd146a3c..000000000 --- a/src/components/instance/clustering/index.js +++ /dev/null @@ -1,77 +0,0 @@ -import React, { useEffect } from 'react'; -import { Code, Row, Col } from '@nio/ui-kit'; -import { useParams } from 'react-router-dom'; -import { useHistory } from 'react-router'; -import stringify from 'json-stringify-pretty-compact'; - -import EntityManager from '../shared/entityManager'; -import useLMS from '../../../stores/lmsData'; -import ClusterCard from './clusterCard'; - -export default ({ auth, structure, network, refreshInstance }) => { - const history = useHistory(); - const [lmsData] = useLMS({ auth: false, instances: [] }); - const { instance_id, schema, table } = useParams(); - - const schemas = structure && Object.keys(structure); - const tables = structure && schemas && structure[schema] && Object.keys(structure[schema]); - - useEffect(() => { - switch (true) { - case (!schemas && history.location.pathname !== '/clustering'): - history.push(`/instances/${instance_id}/clustering`); - break; - case (schemas && schemas.length && !schema): - case (schemas && schemas.length && schema && !schemas.includes(schema)): - history.push(`/instances/${instance_id}/clustering/${schemas[0]}`); - break; - case (tables && tables.length && !table): - case (tables && tables.length && table && !tables.includes(table)): - history.push(`/instances/${instance_id}/clustering/${schema}/${tables[0]}`); - break; - default: - break; - } - }, [schema, schemas, table, tables]); - - return ( - - - - { schema && ( - - )} - - -
- {schema} > {table} > data replication -
- - {lmsData.instances.filter((i) => i.id !== instance_id).map((i) => ( - - ))} - -
- - {stringify(network.outbound_connections, { maxLength: 20 })} - -
- -
- ); -}; diff --git a/src/components/instances.js b/src/components/instances.js new file mode 100644 index 000000000..cdbd730ef --- /dev/null +++ b/src/components/instances.js @@ -0,0 +1,34 @@ +import React from 'react'; +import { Row } from '@nio/ui-kit'; + +import TopNav from './topnav'; +import useLMS from '../stores/lmsData'; +import useInstanceAuth from '../stores/instanceAuths'; +import InstanceCard from './instances/instanceCard'; +import NewInstanceCard from './instances/newInstanceCard'; + +export default () => { + const [lmsData] = useLMS({ auth: false, instances: [] }); + const [instanceAuths, setInstanceAuths] = useInstanceAuth({}); + + return ( + <> + + {lmsData.instances ? ( + + + {lmsData.instances.map((i) => ( + setInstanceAuths({ ...instanceAuths, [id]: { user, pass } })} + /> + ))} + + ) : ( + + )} + + ); +}; diff --git a/src/components/instances/cardBack.js b/src/components/instances/cardBack.js index 235ff4354..bb80e1b8a 100644 --- a/src/components/instances/cardBack.js +++ b/src/components/instances/cardBack.js @@ -3,15 +3,15 @@ import { Button, Card, CardBody, Input, Row, Col } from '@nio/ui-kit'; import useAsyncEffect from 'use-async-effect'; import { useAlert } from 'react-alert'; import queryInstance from '../../util/queryInstance'; -import defaultFormData from '../../util/defaultFormData'; +import defaultFormData from '../../util/defaultInstanceFormData'; -export default ({ id, url, port, is_ssl, setAuth, flipCard }) => { +export default ({ id, host, port, is_ssl, setAuth, flipCard, flipState }) => { const alert = useAlert(); const [formData, updateForm] = useState(defaultFormData); useAsyncEffect(async () => { if (formData.submitted) { - const instance = await queryInstance({ operation: 'describe_all' }, { ...formData, url, port, is_ssl }); + const instance = await queryInstance({ operation: 'describe_all' }, { ...formData, host, port, is_ssl }); if (instance.error) { alert.error(instance.error); updateForm({ ...formData, error: instance.error, submitted: false }); @@ -25,44 +25,46 @@ export default ({ id, url, port, is_ssl, setAuth, flipCard }) => { return ( - - updateForm({ ...formData, user: e.target.value, error: false })} - className="text-center mb-1" - type="text" - title="username" - placeholder="user" - /> - updateForm({ ...formData, pass: e.target.value, error: false })} - className="text-center mb-1" - type="password" - title="password" - placeholder="pass" - /> - - - - - - - - - + {flipState && ( // don't render the forms unless the card is flipped, as the autocomplete icon shows through + + updateForm({ ...formData, user: e.target.value, error: false })} + className="text-center mb-1" + type="text" + title="username" + placeholder="user" + /> + updateForm({ ...formData, pass: e.target.value, error: false })} + className="text-center mb-2" + type="password" + title="password" + placeholder="pass" + /> + + + + + + + + + + )} ); }; diff --git a/src/components/instances/cardFront.js b/src/components/instances/cardFront.js index 9b0522d25..c2928d03b 100644 --- a/src/components/instances/cardFront.js +++ b/src/components/instances/cardFront.js @@ -3,7 +3,7 @@ import { Card, CardBody, Col, Row } from '@nio/ui-kit'; import { useHistory } from 'react-router'; import { useAlert } from 'react-alert'; -export default ({ id, name, url, port, is_ssl, flipCard, setAuth, hasAuth }) => { +export default ({ id, name, host, port, is_ssl, flipCard, setAuth, hasAuth }) => { const history = useHistory(); const alert = useAlert(); @@ -26,7 +26,7 @@ export default ({ id, name, url, port, is_ssl, flipCard, setAuth, hasAuth }) =>
URL - http{is_ssl && 's'}://{url}:{port} + http{is_ssl && 's'}://{host}:{port}
TYPE local diff --git a/src/components/instances/index.js b/src/components/instances/index.js deleted file mode 100644 index 251ef5397..000000000 --- a/src/components/instances/index.js +++ /dev/null @@ -1,25 +0,0 @@ -import React from 'react'; -import { Row } from '@nio/ui-kit'; - -import TopNav from '../topnav'; -import useLMS from '../../stores/lmsData'; -import useInstanceAuth from '../../stores/instanceAuths'; -import InstanceCard from './instanceCard'; -import NewInstanceCard from './newInstanceCard'; - -export default () => { - const [lmsData] = useLMS({ auth: false, instances: [] }); - const [instanceAuths, setInstanceAuths] = useInstanceAuth({}); - - return ( - <> - - - - {lmsData.instances.map((i) => ( - setInstanceAuths({ ...instanceAuths, [id]: { user, pass } })} /> - ))} - - - ); -}; diff --git a/src/components/instances/instanceCard.js b/src/components/instances/instanceCard.js index 3901a1ee6..a063699e1 100644 --- a/src/components/instances/instanceCard.js +++ b/src/components/instances/instanceCard.js @@ -18,6 +18,7 @@ export default (props) => { setFlipState(false)} + flipState={flipState} {...props} /> diff --git a/src/components/instances/newInstanceCard.js b/src/components/instances/newInstanceCard.js index 41cb8d252..c2ff8d075 100644 --- a/src/components/instances/newInstanceCard.js +++ b/src/components/instances/newInstanceCard.js @@ -3,7 +3,7 @@ import { Button, Card, CardBody, Col, Input, Row } from '@nio/ui-kit'; import { useAlert } from 'react-alert'; import useAsyncEffect from 'use-async-effect'; -import defaultFormData from '../../util/defaultFormData'; +import defaultFormData from '../../util/defaultInstanceFormData'; import useLMS from '../../stores/lmsData'; import defaultLMSData from '../../util/defaultLMSData'; import queryLMS from '../../util/queryLMS'; diff --git a/src/components/instance/license/index.js b/src/components/license/index.js similarity index 100% rename from src/components/instance/license/index.js rename to src/components/license/index.js diff --git a/src/components/login.js b/src/components/login.js index 78dfd04e3..d7919ecc0 100644 --- a/src/components/login.js +++ b/src/components/login.js @@ -5,7 +5,7 @@ import { useHistory } from 'react-router'; import useLMS from '../stores/lmsData'; import queryLMS from '../util/queryLMS'; -import defaultFormData from '../util/defaultFormData'; +import defaultFormData from '../util/defaultInstanceFormData'; import defaultLMSData from '../util/defaultLMSData'; export default () => { @@ -14,18 +14,24 @@ export default () => { const history = useHistory(); useAsyncEffect(async () => { - if (formData.submitted) { - const instances = await queryLMS({ - endpoint: 'getInstances', - method: 'GET', - auth: lmsData.auth, - }); - if (instances.error) { - updateForm({ ...formData, error: instances.error, submitted: false }); - setLMSData(defaultLMSData); + const { submitted, user, pass } = formData; + + if (submitted) { + if (user !== 'admin' && pass !== 'Abc1234!') { + updateForm({ ...formData, error: 'unknown user or password', submitted: false }); } else { - setLMSData({ auth: { user: formData.user, pass: formData.pass }, instances }); - history.push('/instances'); + const instances = await queryLMS({ + endpoint: 'getInstances', + method: 'GET', + auth: lmsData.auth, + }); + if (instances.error) { + updateForm({ ...formData, error: instances.error, submitted: false }); + setLMSData(defaultLMSData); + } else { + setLMSData({ auth: { user: formData.user, pass: formData.pass }, instances }); + history.push('/instances'); + } } } }, [formData]); @@ -45,25 +51,23 @@ export default () => { onChange={(e) => updateForm({ ...formData, user: e.target.value })} className="mb-2 text-center" type="text" - name="LMS_USER" - title="Username" - placeholder="Username" + title="username" + placeholder="username" /> updateForm({ ...formData, pass: e.target.value })} className="mb-4 text-center" type="password" - name="LMS_PASS" - title="Password" - placeholder="Password" + title="password" + placeholder="password" /> diff --git a/src/components/instance/roles/index.js b/src/components/roles/index.js similarity index 100% rename from src/components/instance/roles/index.js rename to src/components/roles/index.js diff --git a/src/components/instance/routes.js b/src/components/routes.js similarity index 93% rename from src/components/instance/routes.js rename to src/components/routes.js index 157c45a32..73b42dbce 100644 --- a/src/components/instance/routes.js +++ b/src/components/routes.js @@ -4,10 +4,12 @@ import License from './license'; import Users from './users'; import Roles from './roles'; -export default [ +const routes = [ { component: Browse, path: '/instances/:instance_id/browse/:schema?/:table?/:action?/:hash?', link: 'browse' }, { component: Clustering, path: '/instances/:instance_id/clustering/:schema?/:table?', link: 'clustering' }, { component: License, path: '/instances/:instance_id/license', link: 'license' }, { component: Users, path: '/instances/:instance_id/users', link: 'users' }, { component: Roles, path: '/instances/:instance_id/roles', link: 'roles' }, ]; + +export default routes; diff --git a/src/components/instance/shared/entityManager.js b/src/components/shared/entityManager.js similarity index 100% rename from src/components/instance/shared/entityManager.js rename to src/components/shared/entityManager.js diff --git a/src/components/instance/shared/entityManagerForm.js b/src/components/shared/entityManagerForm.js similarity index 96% rename from src/components/instance/shared/entityManagerForm.js rename to src/components/shared/entityManagerForm.js index 7359c52df..f7c35f53b 100644 --- a/src/components/instance/shared/entityManagerForm.js +++ b/src/components/shared/entityManagerForm.js @@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react'; import { Row, Col, Button, Input, Form } from '@nio/ui-kit'; import { useHistory } from 'react-router'; -import queryInstance from '../../../util/queryInstance'; +import queryInstance from '../../util/queryInstance'; export default ({ items, itemType, activeSchema, toggleDropItem, toggleCreate, baseUrl, auth, refreshInstance }) => { const history = useHistory(); @@ -47,7 +47,7 @@ export default ({ items, itemType, activeSchema, toggleDropItem, toggleCreate, b return setTimeout(() => history.push(`${baseUrl}/${entityName}`), 100); }; - useEffect(() => toggleDropItem(), []); + useEffect(() => toggleDropItem(), [toggleDropItem]); return (
diff --git a/src/components/instance/shared/entityManagerHeader.js b/src/components/shared/entityManagerHeader.js similarity index 100% rename from src/components/instance/shared/entityManagerHeader.js rename to src/components/shared/entityManagerHeader.js diff --git a/src/components/instance/shared/entityManagerRow.js b/src/components/shared/entityManagerRow.js similarity index 98% rename from src/components/instance/shared/entityManagerRow.js rename to src/components/shared/entityManagerRow.js index 0b35880c6..b9f384d99 100644 --- a/src/components/instance/shared/entityManagerRow.js +++ b/src/components/shared/entityManagerRow.js @@ -2,7 +2,7 @@ import React, { useState } from 'react'; import { Row, Col, Button } from '@nio/ui-kit'; import { useHistory, useParams } from 'react-router'; -import queryInstance from '../../../util/queryInstance'; +import queryInstance from '../../util/queryInstance'; export default ({ item, itemType, baseUrl, isActive, toggleDropItem, isDropping, activeSchema, auth, refreshInstance }) => { const history = useHistory(); diff --git a/src/components/topnav.js b/src/components/topnav.js index 54fd87952..9694cad21 100644 --- a/src/components/topnav.js +++ b/src/components/topnav.js @@ -3,15 +3,15 @@ import { Navbar, NavbarToggler, Nav, NavItem, Collapse, NavLink as DumbLink } fr import { useParams, useHistory } from 'react-router'; import { NavLink } from 'react-router-dom'; -import routes from './instance/routes'; import useLMS from '../stores/lmsData'; import defaultLMSData from '../util/defaultLMSData'; -export default () => { +export default ({ routes = false }) => { const { instance_id } = useParams(); const history = useHistory(); const [lmsData, setLMSData] = useLMS(defaultLMSData); const [navOpen, toggleNav] = useState(false); + const thisInstance = lmsData && lmsData.instances.find((i) => i.id === instance_id); return ( @@ -21,7 +21,7 @@ export default () => { toggleNav(!navOpen)} isOpen={navOpen} />
); diff --git a/src/components/clustering/manage.js b/src/components/clustering/manage.js index 9d55c85dd..a856e64d4 100644 --- a/src/components/clustering/manage.js +++ b/src/components/clustering/manage.js @@ -68,27 +68,23 @@ export default ({ auth, network, refreshInstance, structure }) => {
- {schema} > {table} > data replication + {schema} > {table}
- {lmsData.instances.filter((i) => i.id !== instance_id).map((i) => { - const connection = network && network.outbound_connections.find((n) => n.node_name === i.node_name); - const pub = schema && table && connection && connection.subscriptions && connection.subscriptions.find((s) => s.channel === `${schema}:${table}` && s.publish); - const sub = schema && table && connection && connection.subscriptions && connection.subscriptions.find((s) => s.channel === `${schema}:${table}` && s.subscribe); - return ( - setInstanceAuths({ ...instanceAuths, [id]: { user, pass } })} - /> - ); - })} + {lmsData.instances.filter((i) => i.id !== instance_id).map((i) => ( + n.name === i.id)} + clusterPort={12345} /* TODO: use a new getter to get this value */ + schema={schema} + table={table} + refreshInstance={refreshInstance} + auth={auth} + instanceAuth={instanceAuths[i.id]} + setAuth={({ id, user, pass }) => setInstanceAuths({ ...instanceAuths, [id]: { user, pass } })} + /> + ))}
diff --git a/src/components/clustering/manage_cardFront.js b/src/components/clustering/manage_cardFront.js index 28c6580a3..4d494adcb 100644 --- a/src/components/clustering/manage_cardFront.js +++ b/src/components/clustering/manage_cardFront.js @@ -2,31 +2,29 @@ import React, { useState } from 'react'; import { Button, Card, CardBody, Col, Row } from '@nio/ui-kit'; import useAsyncEffect from 'use-async-effect'; -import queryInstance from '../../util/queryInstance'; +import updateInstanceSubscription from '../../util/updateInstanceSubscription'; +import connectToInstance from '../../util/connectToInstance'; -export default ({ id, host, port, name, node_name, is_ssl, connection, pub, sub, hasAuth, flipCard, refreshInstance, auth }) => { +export default ({ id, host, port, name, is_ssl, connection, clusterPort, schema, table, instanceAuth, flipCard, refreshInstance, auth }) => { const [buttonState, setButtonState] = useState(false); + const [isConnected, setIsConnected] = useState(false); + + const subscriptions = schema && table && connection && connection.subscriptions ? connection.subscriptions : []; + const channel = schema && table && `${schema}:${table}`; + const channelSubscription = channel && subscriptions.find((s) => s.channel === channel); + const publish = channelSubscription && channelSubscription.publish; + const subscribe = channelSubscription && channelSubscription.subscribe; useAsyncEffect(async () => { if (buttonState) { if (buttonState === 'connect') { - const response = await queryInstance({ operation: 'add_node', name: id, host, port }, auth); - console.log(response); - refreshInstance(Date.now()); - setButtonState(false); - } - if (buttonState === 'pub') { - const response = await queryInstance({ operation: 'update_node', name: id, host, port }, auth); - console.log(response); - refreshInstance(Date.now()); - setButtonState(false); - } - if (buttonState === 'sub') { - const response = await queryInstance({ operation: 'update_node', name: id, host, port }, auth); - console.log(response); - refreshInstance(Date.now()); - setButtonState(false); + await connectToInstance({ id, host, port, clusterPort, auth }); + setIsConnected(true); + } else { + await updateInstanceSubscription({ channel, subscriptions, buttonState, id, host, clusterPort, auth }); } + setTimeout(() => refreshInstance(Date.now(), 1000)); + setButtonState(false); } }, [buttonState]); @@ -38,7 +36,7 @@ export default ({ id, host, port, name, node_name, is_ssl, connection, pub, sub, {name} - {hasAuth ? ( + {instanceAuth ? ( ) : ( @@ -46,33 +44,30 @@ export default ({ id, host, port, name, node_name, is_ssl, connection, pub, sub,
- + URL http{is_ssl && 's'}://{host}:{port}
- Node Name - {node_name} -
- {!hasAuth ? ( + {!instanceAuth ? ( - ) : !connection ? ( + ) : !connection && !isConnected ? ( - + ) : ( - + - + )} diff --git a/src/components/instances.js b/src/components/instances.js index cdbd730ef..77f93c40d 100644 --- a/src/components/instances.js +++ b/src/components/instances.js @@ -21,8 +21,8 @@ export default () => { setInstanceAuths({ ...instanceAuths, [id]: { user, pass } })} + hasAuth={instanceAuths[i.id]} + setAuth={({ id, user, pass }) => setInstanceAuths({ ...instanceAuths, [id]: user && pass ? { user, pass } : false })} /> ))}
diff --git a/src/components/instances/cardFront.js b/src/components/instances/cardFront.js index c2928d03b..6239d711a 100644 --- a/src/components/instances/cardFront.js +++ b/src/components/instances/cardFront.js @@ -11,7 +11,7 @@ export default ({ id, name, host, port, is_ssl, flipCard, setAuth, hasAuth }) => (hasAuth ? history.push(`/instances/${id}/browse`) : alert.error('You must log in first.') && flipCard())}> - + {name} diff --git a/src/components/roles/index.js b/src/components/roles/index.js index 3a569d8f5..20f2a2513 100644 --- a/src/components/roles/index.js +++ b/src/components/roles/index.js @@ -1,15 +1,19 @@ import React from 'react'; -import { Card, CardBody, Row, Col } from '@nio/ui-kit'; +import { Code } from '@nio/ui-kit'; -export default () => ( - - - Roles - - - content here - - - - -); +export default ({ auth, structure, network, refreshInstance }) => (!network ? ( + +) : network.is_enabled && network.cluster_user && network.cluster_role ? ( + +) : ( + +)); diff --git a/src/index.js b/src/index.js index 47ca3ef48..ae32e72a3 100644 --- a/src/index.js +++ b/src/index.js @@ -2,10 +2,14 @@ import React from 'react'; import { render } from 'react-dom'; import { BrowserRouter } from 'react-router-dom'; import { positions, Provider } from 'react-alert'; +import { TextDecoder } from 'text-encoding'; + import AlertTemplate from './components/shared/alert'; import App from './components/app'; import './app.scss'; +if (!window.TextDecoder) window.TextDecoder = TextDecoder; + render( ( diff --git a/src/util/connectToInstance.js b/src/util/connectToInstance.js new file mode 100644 index 000000000..b51fe02a4 --- /dev/null +++ b/src/util/connectToInstance.js @@ -0,0 +1,6 @@ +import queryInstance from './queryInstance'; + +export default async ({ id, host, clusterPort, auth }) => { + await queryInstance({ operation: 'remove_node', name: id }, auth); + return queryInstance({ operation: 'add_node', name: id, host, port: clusterPort, subscriptions: [] }, auth); +}; diff --git a/src/util/getInstanceClusterStatus.js b/src/util/getInstanceClusterStatus.js index 6f58dba31..c365a02a2 100644 --- a/src/util/getInstanceClusterStatus.js +++ b/src/util/getInstanceClusterStatus.js @@ -1,5 +1,12 @@ import queryInstance from './queryInstance'; +const processConnections = (connections) => (connections ? connections.filter((c) => c.host_address.indexOf('::ffff') === -1).map((c) => ({ + name: c.node_name, + host: c.host_address, + port: c.host_port, + subscriptions: c.subscriptions, +})) : []); + export default async (auth) => { const clusterResponse = await queryInstance({ operation: 'cluster_status' }, auth); const instanceRoles = await queryInstance({ operation: 'list_roles' }, auth); @@ -24,8 +31,8 @@ export default async (auth) => { is_enabled, cluster_role: cluster_role && cluster_role.id, cluster_user: cluster_user && cluster_user.username, - inbound_connections: inbound_connections ? inbound_connections.filter((c) => c.host_address.indexOf('::ffff') === -1) : [], - outbound_connections: outbound_connections ? outbound_connections.filter((c) => c.host_address.indexOf('::ffff') === -1) : [], + inbound_connections: processConnections(inbound_connections), + outbound_connections: processConnections(outbound_connections), name: __originator && Object.keys(__originator)[0], }; }; diff --git a/src/util/updateInstanceSubscription.js b/src/util/updateInstanceSubscription.js new file mode 100644 index 000000000..789a81618 --- /dev/null +++ b/src/util/updateInstanceSubscription.js @@ -0,0 +1,31 @@ +import queryInstance from './queryInstance'; + +export default async ({ channel, subscriptions, buttonState, id, host, clusterPort, auth }) => { + const existingChannelSubscriptionIndex = subscriptions.findIndex((s) => s.channel === channel); + + // if we have no subscription for this node, add it to the subscriptions array + if (existingChannelSubscriptionIndex === -1) { + subscriptions.push({ + channel, + publish: buttonState === 'togglePublish', + subscribe: buttonState === 'toggleSubscribe', + }); + + // if we're changing the publish status + } else if (buttonState === 'togglePublish') { + subscriptions[existingChannelSubscriptionIndex].publish = !subscriptions[existingChannelSubscriptionIndex].publish; + + // if we're changing the subscribe status + } else if (buttonState === 'toggleSubscribe') { + subscriptions[existingChannelSubscriptionIndex].subscribe = !subscriptions[existingChannelSubscriptionIndex].subscribe; + } + + // get rid of subscription node if it's neither publishing or subscribing + if (subscriptions[existingChannelSubscriptionIndex] && !subscriptions[existingChannelSubscriptionIndex].publish && !subscriptions[existingChannelSubscriptionIndex].subscribe) { + subscriptions.splice(existingChannelSubscriptionIndex, 1); + } + + // send the query + const operation = { operation: 'update_node', name: id, host, port: clusterPort, subscriptions }; + return queryInstance(operation, auth); +}; From 4bd3d2f6c4fbdc5431cb874bcf4bbffeb3475d82 Mon Sep 17 00:00:00 2001 From: deliciousmonster Date: Thu, 13 Feb 2020 16:01:55 -0700 Subject: [PATCH 08/19] clustering --- src/assets/styles/_card.scss | 18 ++++- src/components/clustering/clusterCard.js | 72 +++++++++++++++++ src/components/clustering/manage.js | 4 +- src/components/clustering/manage_cardBack.js | 70 ----------------- src/components/clustering/manage_cardFront.js | 77 ------------------- .../clustering/manage_clusterCard.js | 27 ------- 6 files changed, 92 insertions(+), 176 deletions(-) create mode 100644 src/components/clustering/clusterCard.js delete mode 100644 src/components/clustering/manage_cardBack.js delete mode 100644 src/components/clustering/manage_cardFront.js delete mode 100644 src/components/clustering/manage_clusterCard.js diff --git a/src/assets/styles/_card.scss b/src/assets/styles/_card.scss index b6c0feea1..be4db05ca 100644 --- a/src/assets/styles/_card.scss +++ b/src/assets/styles/_card.scss @@ -17,7 +17,8 @@ } } - &.instance { + &.instance, + &.manage { height: 140px; cursor: pointer; @@ -33,4 +34,19 @@ } } } + + &.manage { + height: 49px; + margin-bottom: 1px; + + .card-body { + padding-top: 7px; + padding-bottom: 0; + + .instance-name { + line-height: 1; + padding-top: 3px; + } + } + } } diff --git a/src/components/clustering/clusterCard.js b/src/components/clustering/clusterCard.js new file mode 100644 index 000000000..54f7128d6 --- /dev/null +++ b/src/components/clustering/clusterCard.js @@ -0,0 +1,72 @@ +import React, { useState } from 'react'; +import { Button, Card, CardBody, Col, Row } from '@nio/ui-kit'; +import useAsyncEffect from 'use-async-effect'; + +import connectToInstance from '../../util/connectToInstance'; +import updateInstanceSubscription from '../../util/updateInstanceSubscription'; + +export default ({ id, host, port, name, is_ssl, connection, clusterPort, schema, table, instanceAuth, refreshInstance, auth, setAuth }) => { + const [buttonState, setButtonState] = useState(false); + const [isConnected, setIsConnected] = useState(false); + const [isLoading, setIsLoading] = useState(false); + + const subscriptions = schema && table && connection && connection.subscriptions ? connection.subscriptions : []; + const channel = schema && table && `${schema}:${table}`; + const channelSubscription = channel && subscriptions.find((s) => s.channel === channel); + const publish = channelSubscription && channelSubscription.publish; + const subscribe = channelSubscription && channelSubscription.subscribe; + + useAsyncEffect(async () => { + if (buttonState && !isLoading) { + setIsLoading(true); + if (buttonState === 'connect') { + await connectToInstance({ id, host, port, clusterPort, auth }); + setIsConnected(true); + } else { + await updateInstanceSubscription({ channel, subscriptions, buttonState, id, host, clusterPort, auth }); + } + setTimeout(() => { + refreshInstance(Date.now()); + setButtonState(false); + setIsLoading(false); + }, 1000); + } + }, [buttonState]); + + return ( + + + + + + {name}
+ http{is_ssl && 's'}://{host}:{port} + + {!connection && !isConnected ? ( + + + + ) : ( + + + + + + + + + + + )} +
+
+
+ + ); +}; diff --git a/src/components/clustering/manage.js b/src/components/clustering/manage.js index a856e64d4..bc4a63ff1 100644 --- a/src/components/clustering/manage.js +++ b/src/components/clustering/manage.js @@ -7,7 +7,7 @@ import useAsyncEffect from 'use-async-effect'; import EntityManager from '../shared/entityManager'; import useLMS from '../../stores/lmsData'; -import ClusterCard from './manage_clusterCard'; +import ClusterCard from './clusterCard'; import useInstanceAuth from '../../stores/instanceAuths'; export default ({ auth, network, refreshInstance, structure }) => { @@ -86,11 +86,13 @@ export default ({ auth, network, refreshInstance, structure }) => { /> ))}
+ {/*
{stringify(network.outbound_connections, { maxLength: 20 })}
+ */} ); diff --git a/src/components/clustering/manage_cardBack.js b/src/components/clustering/manage_cardBack.js deleted file mode 100644 index 00af2a965..000000000 --- a/src/components/clustering/manage_cardBack.js +++ /dev/null @@ -1,70 +0,0 @@ -import React, { useState } from 'react'; -import { Button, Card, CardBody, Input, Row, Col } from '@nio/ui-kit'; -import useAsyncEffect from 'use-async-effect'; -import { useAlert } from 'react-alert'; -import queryInstance from '../../util/queryInstance'; -import defaultFormData from '../../util/defaultInstanceFormData'; - -export default ({ id, host, port, is_ssl, setAuth, flipCard, flipState }) => { - const alert = useAlert(); - const [formData, updateForm] = useState(defaultFormData); - - useAsyncEffect(async () => { - if (formData.submitted) { - const instance = await queryInstance({ operation: 'describe_all' }, { ...formData, host, port, is_ssl }); - if (instance.error) { - alert.error(instance.error); - updateForm({ ...formData, error: instance.error, submitted: false }); - } else { - updateForm(defaultFormData); - setAuth({ id, user: formData.user, pass: formData.pass }); - flipCard(); - } - } - }, [formData]); - - return ( - - {flipState && ( - - updateForm({ ...formData, user: e.target.value, error: false })} - className="text-center mb-1" - type="text" - title="username" - placeholder="user" - /> - updateForm({ ...formData, pass: e.target.value, error: false })} - className="text-center mb-2" - type="password" - title="password" - placeholder="pass" - /> - - - - - - - - - - )} - - ); -}; diff --git a/src/components/clustering/manage_cardFront.js b/src/components/clustering/manage_cardFront.js deleted file mode 100644 index 4d494adcb..000000000 --- a/src/components/clustering/manage_cardFront.js +++ /dev/null @@ -1,77 +0,0 @@ -import React, { useState } from 'react'; -import { Button, Card, CardBody, Col, Row } from '@nio/ui-kit'; -import useAsyncEffect from 'use-async-effect'; - -import updateInstanceSubscription from '../../util/updateInstanceSubscription'; -import connectToInstance from '../../util/connectToInstance'; - -export default ({ id, host, port, name, is_ssl, connection, clusterPort, schema, table, instanceAuth, flipCard, refreshInstance, auth }) => { - const [buttonState, setButtonState] = useState(false); - const [isConnected, setIsConnected] = useState(false); - - const subscriptions = schema && table && connection && connection.subscriptions ? connection.subscriptions : []; - const channel = schema && table && `${schema}:${table}`; - const channelSubscription = channel && subscriptions.find((s) => s.channel === channel); - const publish = channelSubscription && channelSubscription.publish; - const subscribe = channelSubscription && channelSubscription.subscribe; - - useAsyncEffect(async () => { - if (buttonState) { - if (buttonState === 'connect') { - await connectToInstance({ id, host, port, clusterPort, auth }); - setIsConnected(true); - } else { - await updateInstanceSubscription({ channel, subscriptions, buttonState, id, host, clusterPort, auth }); - } - setTimeout(() => refreshInstance(Date.now(), 1000)); - setButtonState(false); - } - }, [buttonState]); - - return ( - - - - - {name} - - - {instanceAuth ? ( - - ) : ( - - )} - - -
- - URL - http{is_ssl && 's'}://{host}:{port} -
-
- {!instanceAuth ? ( - - - - - - ) : !connection && !isConnected ? ( - - - - - - ) : ( - - - - - - - - - )} -
-
- ); -}; diff --git a/src/components/clustering/manage_clusterCard.js b/src/components/clustering/manage_clusterCard.js deleted file mode 100644 index c0303201e..000000000 --- a/src/components/clustering/manage_clusterCard.js +++ /dev/null @@ -1,27 +0,0 @@ -import React, { useState } from 'react'; -import ReactCardFlip from 'react-card-flip'; -import { Col } from '@nio/ui-kit'; - -import CardFront from './manage_cardFront'; -import CardBack from './manage_cardBack'; - -export default (props) => { - const [flipState, setFlipState] = useState(false); - - return ( - - - setFlipState(true)} - {...props} - /> - - setFlipState(false)} - flipState={flipState} - {...props} - /> - - - ); -}; From 4e05cd0ae604401b228d023d922ad9372b04fd43 Mon Sep 17 00:00:00 2001 From: deliciousmonster Date: Wed, 19 Feb 2020 18:14:35 -0700 Subject: [PATCH 09/19] add Instance, rework clustering --- .babelrc | 1 + .eslintrc | 1 + mock_data/LMS_API.aws_regions.json | 78 +++++++++ mock_data/LMS_API.instances.json | 88 ++++++++-- mock_data/LMS_API.products.json | 26 +++ package-lock.json | 18 +-- package.json | 5 +- src/app.scss | 1 + src/assets/styles/_button.scss | 10 ++ src/assets/styles/_dropdown.scss | 92 +++++------ src/assets/styles/_forms.scss | 23 ++- src/assets/styles/_modal.scss | 30 ++++ src/assets/styles/_vars.scss | 1 + src/components/app.js | 25 +-- src/components/browse/index.js | 50 +++--- src/components/clustering/datatable.js | 65 ++++++++ src/components/clustering/manage.js | 48 +++--- src/components/instances.js | 30 ++-- src/components/instances/cardBack.js | 2 +- src/components/instances/cloudInstanceForm.js | 117 ++++++++++++++ src/components/instances/localInstanceForm.js | 150 ++++++++++++++++++ src/components/instances/newInstanceCard.js | 81 +--------- src/components/instances/newInstanceModal.js | 62 ++++++++ src/components/login.js | 6 +- src/index.html | 1 + src/index.js | 1 - src/util/clusterConfigColumns.js | 36 +++++ src/util/connectToInstance.js | 5 +- src/util/defaultAuthFormData.js | 1 + src/util/defaultInstanceFormData.js | 12 +- src/util/getInstanceDataStructure.js | 1 + src/util/handleSchemaTableRedirect.js | 17 ++ src/util/queryLMS.js | 10 +- src/util/setStructureEntities.js | 6 + src/util/updateInstanceSubscription.js | 5 +- 35 files changed, 861 insertions(+), 244 deletions(-) create mode 100644 mock_data/LMS_API.aws_regions.json create mode 100644 mock_data/LMS_API.products.json create mode 100644 src/assets/styles/_modal.scss create mode 100644 src/components/clustering/datatable.js create mode 100644 src/components/instances/cloudInstanceForm.js create mode 100644 src/components/instances/localInstanceForm.js create mode 100644 src/components/instances/newInstanceModal.js create mode 100644 src/util/clusterConfigColumns.js create mode 100644 src/util/defaultAuthFormData.js create mode 100644 src/util/handleSchemaTableRedirect.js create mode 100644 src/util/setStructureEntities.js diff --git a/.babelrc b/.babelrc index 6488b0769..ab3dedbb3 100644 --- a/.babelrc +++ b/.babelrc @@ -1,6 +1,7 @@ { "presets": ["@babel/preset-react", "@babel/preset-env"], "plugins": [ + "@babel/plugin-proposal-optional-chaining", ["transform-imports", { "@nio/ui-kit": { "transform": "@nio/ui-kit/dist/components/${member}", "preventFullImport": true } }] ] } diff --git a/.eslintrc b/.eslintrc index cddb1d7dd..77d1940da 100644 --- a/.eslintrc +++ b/.eslintrc @@ -8,6 +8,7 @@ "react-hooks" ], "rules": { + "strict": 0, "react-hooks/rules-of-hooks": "error", "react-hooks/exhaustive-deps": "warn", "camelcase": 0, diff --git a/mock_data/LMS_API.aws_regions.json b/mock_data/LMS_API.aws_regions.json new file mode 100644 index 000000000..31b9ef59c --- /dev/null +++ b/mock_data/LMS_API.aws_regions.json @@ -0,0 +1,78 @@ +[ + { + "label": "US East (N. Virginia)", + "value": "us-east-1" + }, + { + "label": "US East (Ohio)", + "value": "us-east-2" + }, + { + "label": "US West (N. California)", + "value": "us-west-1" + }, + { + "label": "US West (Oregon)", + "value": "us-west-2" + }, + { + "label": "Asia Pacific (Hong Kong)", + "value": "ap-east-1" + }, + { + "label": "Asia Pacific (Mumbai)", + "value": "ap-south-1" + }, + { + "label": "Asia Pacific (Osaka-Local)", + "value": "ap-northeast-3" + }, + { + "label": "Asia Pacific (Seoul)", + "value": "ap-northeast-2" + }, + { + "label": "Asia Pacific (Singapore)", + "value": "ap-southeast-1" + }, + { + "label": "Asia Pacific (Sydney)", + "value": "ap-southeast-2" + }, + { + "label": "Asia Pacific (Tokyo)", + "value": "ap-northeast-1" + }, + { + "label": "Canada (Central)", + "value": "ca-central-1" + }, + { + "label": "Europe (Frankfurt)", + "value": "eu-central-1" + }, + { + "label": "Europe (Ireland)", + "value": "eu-west-1" + }, + { + "label": "Europe (London)", + "value": "eu-west-2" + }, + { + "label": "Europe (Paris)", + "value": "eu-west-3" + }, + { + "label": "Europe (Stockholm)", + "value": "eu-north-1" + }, + { + "label": "Middle East (Bahrain)", + "value": "me-south-1" + }, + { + "label": "South America (São Paulo)", + "value": "sa-east-1" + } +] diff --git a/mock_data/LMS_API.instances.json b/mock_data/LMS_API.instances.json index 893d272e4..17694b38f 100644 --- a/mock_data/LMS_API.instances.json +++ b/mock_data/LMS_API.instances.json @@ -1,66 +1,122 @@ [ { "id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", - "name": "3f6c9e35f6a84fb7b09f8b3e7d4e20df", + "instance_name": "3f6c9e35f6a84fb7b09f8b3e7d4e20df", "host": "localhost", "port": 9925, "cluster_port": 12345, - "is_ssl": false + "is_ssl": false, + "is_local": true, + "status": "ready", + "instance_region": "us-east", + "domain_name": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df.cloud.harperdb.io", + "elastic_ip_address": "172.168.2.12", + "customer_id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", + "stripe_product_id": "product_123412341234" }, { "id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20de", - "name": "ClusterDemoA1", + "instance_name": "ClusterDemoA1", "host": "ClusterDemoA1", "port": 9925, "cluster_port": 12345, - "is_ssl": false + "is_ssl": false, + "is_local": true, + "status": "ready", + "instance_region": "us-east", + "domain_name": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df.cloud.harperdb.io", + "elastic_ip_address": "172.168.2.12", + "customer_id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", + "stripe_product_id": "product_123412341234" }, { "id": "cee98941-8975-44d6-88e6-9a1a4fa02b25", - "name": "ClusterDemoA2", + "instance_name": "ClusterDemoA2", "host": "ClusterDemoA2", "port": 9925, "cluster_port": 12345, - "is_ssl": false + "is_ssl": false, + "is_local": true, + "status": "ready", + "instance_region": "us-east", + "domain_name": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df.cloud.harperdb.io", + "elastic_ip_address": "172.168.2.12", + "customer_id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", + "stripe_product_id": "product_123412341234" }, { "id": "a32b2464-b5ca-4b9d-a10f-c6da23a7dcbe", - "name": "ClusterDemoA3", + "instance_name": "ClusterDemoA3", "host": "ClusterDemoA3", "port": 9925, "cluster_port": 12345, - "is_ssl": false + "is_ssl": false, + "is_local": true, + "status": "ready", + "instance_region": "us-east", + "domain_name": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df.cloud.harperdb.io", + "elastic_ip_address": "172.168.2.12", + "customer_id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", + "stripe_product_id": "product_123412341234" }, { "id": "7a60b51d-7983-4659-a34b-cfb578a4db54", - "name": "ClusterDemoA4", + "instance_name": "ClusterDemoA4", "host": "ClusterDemoA4", "port": 9925, "cluster_port": 12345, - "is_ssl": false + "is_ssl": false, + "is_local": true, + "status": "ready", + "instance_region": "us-east", + "domain_name": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df.cloud.harperdb.io", + "elastic_ip_address": "172.168.2.12", + "customer_id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", + "stripe_product_id": "product_123412341234" }, { "id": "f0d29404-9fb1-4d66-ab98-41f05e1ac665", - "name": "ClusterDemoA5", + "instance_name": "ClusterDemoA5", "host": "ClusterDemoA5", "port": 9925, "cluster_port": 12345, - "is_ssl": false + "is_ssl": false, + "is_local": true, + "status": "ready", + "instance_region": "us-east", + "domain_name": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df.cloud.harperdb.io", + "elastic_ip_address": "172.168.2.12", + "customer_id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", + "stripe_product_id": "product_123412341234" }, { "id": "ccf2b958-0b0e-4bf6-a81d-6abee1e4eec5", - "name": "ClusterDemoA6", + "instance_name": "ClusterDemoA6", "host": "ClusterDemoA6", "port": 9925, "cluster_port": 12345, - "is_ssl": false + "is_ssl": false, + "is_local": true, + "status": "ready", + "instance_region": "us-east", + "domain_name": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df.cloud.harperdb.io", + "elastic_ip_address": "172.168.2.12", + "customer_id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", + "stripe_product_id": "product_123412341234" }, { "id": "8291c224-6911-4155-b5a9-34559e5a45ff", - "name": "ClusterDemoA7", + "instance_name": "ClusterDemoA7", "host": "ClusterDemoA7", "port": 9925, "cluster_port": 12345, - "is_ssl": false + "is_ssl": false, + "is_local": true, + "status": "ready", + "instance_region": "us-east", + "domain_name": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df.cloud.harperdb.io", + "elastic_ip_address": "172.168.2.12", + "customer_id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", + "stripe_product_id": "product_123412341234" } ] diff --git a/mock_data/LMS_API.products.json b/mock_data/LMS_API.products.json new file mode 100644 index 000000000..74d5aacd1 --- /dev/null +++ b/mock_data/LMS_API.products.json @@ -0,0 +1,26 @@ +[ + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", + "stripe_product_id": "product_1", + "instance_ram": 1, + "instance_disk_space_gigs": 100, + "cloud_aws_instance_type": "AWS-Instance-Type-1", + "cloud_price_monthly": 0, + "cloud_price_annual": 0, + "local_price_monthly": 0, + "local_price_annual": 0, + "is_active": true + }, + { + "id": "3fa85f64-5717-4562-b3fc-2c963f66afa7", + "stripe_product_id": "product_2", + "instance_ram": 2, + "instance_disk_space_gigs": 100, + "cloud_aws_instance_type": "AWS-Instance-Type-2", + "cloud_price_monthly": 19.99, + "cloud_price_annual": 199.99, + "local_price_monthly": 9.99, + "local_price_annual": 99.99, + "is_active": true + } +] diff --git a/package-lock.json b/package-lock.json index b1bc51b27..5e719f61f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6956,9 +6956,9 @@ } }, "eslint-plugin-react-hooks": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-2.3.0.tgz", - "integrity": "sha512-gLKCa52G4ee7uXzdLiorca7JIQZPPXRAQDXV83J4bUEeUuc5pIEyZYAZ45Xnxe5IuupxEqHS+hUhSLIimK1EMw==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-2.4.0.tgz", + "integrity": "sha512-bH5DOCP6WpuOqNaux2BlaDCrSgv8s5BitP90bTgtZ1ZsRn2bdIfeMDY5F2RnJVnyKDy6KRQRDbipPLZ1y77QtQ==", "dev": true }, "eslint-scope": { @@ -16627,9 +16627,9 @@ } }, "terser-webpack-plugin": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-2.3.4.tgz", - "integrity": "sha512-Nv96Nws2R2nrFOpbzF6IxRDpIkkIfmhvOws+IqMvYdFLO7o6wAILWFKONFgaYy8+T4LVz77DQW0f7wOeDEAjrg==", + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-2.3.5.tgz", + "integrity": "sha512-WlWksUoq+E4+JlJ+h+U+QUzXpcsMSSNXkDy9lBVkSqDn1w23Gg29L/ary9GeJVYCGiNJJX7LnVc4bwL1N3/g1w==", "dev": true, "requires": { "cacache": "^13.0.1", @@ -16709,9 +16709,9 @@ } }, "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==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.2.tgz", + "integrity": "sha512-rYKABKutXa6vXTXhoV18cBE7PaewPXHe/Bdq4v+ZLMhxbWApkFFplT0LcbMW+6BbjnQXzZ/sAvSE/JdguApG5w==", "dev": true, "requires": { "semver": "^6.0.0" diff --git a/package.json b/package.json index b118e2b79..42863298b 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "devDependencies": { "@babel/cli": "^7.8.4", "@babel/core": "^7.8.4", + "@babel/plugin-proposal-optional-chaining": "^7.8.3", "@babel/polyfill": "^7.8.3", "@babel/preset-env": "^7.8.4", "@babel/preset-react": "^7.8.3", @@ -52,7 +53,7 @@ "eslint-plugin-import": "^2.20.1", "eslint-plugin-jsx-a11y": "^6.2.3", "eslint-plugin-react": "^7.18.3", - "eslint-plugin-react-hooks": "^2.3.0", + "eslint-plugin-react-hooks": "^2.4.0", "file-loader": "^5.0.2", "html-loader": "^0.5.5", "html-webpack-inline-source-plugin": "0.0.10", @@ -65,7 +66,7 @@ "postcss-loader": "^3.0.0", "sass-lint": "^1.13.1", "sass-loader": "^8.0.2", - "terser-webpack-plugin": "^2.3.4", + "terser-webpack-plugin": "^2.3.5", "webpack": "^4.41.6", "webpack-cli": "^3.3.11", "webpack-dev-server": "^3.10.3", diff --git a/src/app.scss b/src/app.scss index b87c5ed35..2e293a928 100644 --- a/src/app.scss +++ b/src/app.scss @@ -23,6 +23,7 @@ @import './assets/styles/login'; @import './assets/styles/csv-input'; @import './assets/styles/clustering'; +@import './assets/styles/modal'; @import '~font-awesome/scss/font-awesome'; diff --git a/src/assets/styles/_button.scss b/src/assets/styles/_button.scss index 883d9e5de..5186fe3d9 100644 --- a/src/assets/styles/_button.scss +++ b/src/assets/styles/_button.scss @@ -15,6 +15,12 @@ height: 25px !important; } + &.connect { + padding: 0 !important; + height: 25px !important; + font-size: 12px; + } + &.btn-link { padding: 0; } @@ -50,6 +56,10 @@ } } +.btn-group { + width: 100%; +} + .btn-group>.btn-group:not(:last-child)>.btn, .btn-group>.btn:not(:last-child):not(.dropdown-toggle) { border-top-right-radius: 0 !important; diff --git a/src/assets/styles/_dropdown.scss b/src/assets/styles/_dropdown.scss index 846501e37..00d1d3b51 100644 --- a/src/assets/styles/_dropdown.scss +++ b/src/assets/styles/_dropdown.scss @@ -1,60 +1,60 @@ -.dropdown { - width: 100%; +#app-nav { + .dropdown { + width: 100%; - &.nav-item { - width: 250px; - } + &.nav-item { + width: 250px; + } - span { - display: block; - text-overflow: ellipsis; - overflow: hidden; - padding-right: 15px; - white-space: nowrap; - } + span { + display: block; + text-overflow: ellipsis; + overflow: hidden; + padding-right: 15px; + white-space: nowrap; + } - .dropdown-menu { - width: 100%; - border-radius: 6px; - background-color: $color-black !important; - z-index: 9999; - margin: 0; - padding: 0; - - .dropdown-item { - color: $color-white; - border-bottom: 1px solid $white-overlay; - padding: .5rem 1.5rem; - background-color: $color-black !important; + .dropdown-menu { + width: 100%; border-radius: 6px; + background-color: $color-black !important; + z-index: 9999; + margin: 0; + padding: 0; - span { - padding-right: 0; - } + .dropdown-item { + color: $color-white; + border-bottom: 1px solid $white-overlay; + padding: .5rem 1.5rem; + background-color: $color-black !important; + border-radius: 6px; - &:last-child { - border-bottom: 0; - } + span { + padding-right: 0; + } - &:hover { - background-color: $color-black !important; - color: $color-white; + &:last-child { + border-bottom: 0; + } + + &:hover { + background-color: $color-black !important; + color: $color-white; + } } } - } - .dropdown-toggle { - padding-left: 1.5rem; - width: 100%; - text-align: left; - position: relative; + .dropdown-toggle { + padding-left: 1.5rem; + width: 100%; + text-align: left; + position: relative; - &::after { - position: absolute; - top: 15px; - right: 15px; + &::after { + position: absolute; + top: 15px; + right: 15px; + } } } - - } diff --git a/src/assets/styles/_forms.scss b/src/assets/styles/_forms.scss index 877e2ada3..598e73f5d 100644 --- a/src/assets/styles/_forms.scss +++ b/src/assets/styles/_forms.scss @@ -4,7 +4,7 @@ input:-webkit-autofill, input:-webkit-autofill:hover, input:-webkit-autofill:focus, input:-webkit-autofill:active { - border: 0 !important; + border: 1px solid $grey-overlay; background: $white-overlay !important; color: $color-black !important; font-family: $font-family-sans; @@ -26,3 +26,24 @@ input:-webkit-autofill:active { } + + + +.nio-check-mark:before { + content: "\f00c"; +} + +.nio:before { + font-family: 'FontAwesome'!important; + font-style: normal; + font-weight: 400!important; + font-variant: normal; + text-transform: none; + vertical-align: top; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + + + + diff --git a/src/assets/styles/_modal.scss b/src/assets/styles/_modal.scss new file mode 100644 index 000000000..2bab4d098 --- /dev/null +++ b/src/assets/styles/_modal.scss @@ -0,0 +1,30 @@ +#new-instance-modal { + .modal-footer { + display: block !important; + } + + .new-instance-label { + font-weight: 700; + margin-top: 20px; + margin-bottom: 5px; + } + + .radio-button > div { + border-bottom: 1px solid $light-grey-overlay; + padding: 8px 0; + + &:last-child { + border-bottom: none; + padding-bottom: 4px; + } + } + + .fieldset { + border: 1px solid $light-grey-overlay; + padding: 5px 15px; + border-radius: 4px; + max-height: 124px; + overflow-x: hidden; + overflow-y: auto; + } +} diff --git a/src/assets/styles/_vars.scss b/src/assets/styles/_vars.scss index ea613bc60..5f09d4803 100644 --- a/src/assets/styles/_vars.scss +++ b/src/assets/styles/_vars.scss @@ -18,6 +18,7 @@ $gradient-purple-magenta: linear-gradient(45deg, $color-darkpurple, $color-purpl $white-overlay: rgba(255, 255, 255, .3); $light-white-overlay: rgba(255, 255, 255, .7); +$light-grey-overlay: rgba(0, 0, 0, .05); $grey-overlay: rgba(0, 0, 0, .2); $dark-grey-overlay: rgba(0, 0, 0, .5); $black-overlay: rgba(0, 0, 0, .8); diff --git a/src/components/app.js b/src/components/app.js index 55ea03a4c..c471923e5 100644 --- a/src/components/app.js +++ b/src/components/app.js @@ -4,14 +4,19 @@ import { Route, Switch, Redirect } from 'react-router-dom'; import Instances from './instances'; import Instance from './instance'; import Login from './login'; +import useLMS from '../stores/lmsData'; -export default () => ( -
- - - - - - -
-); +export default () => { + const [lmsData] = useLMS({ auth: false, instances: [] }); + + return ( +
+ + + (lmsData && lmsData.instances ? : )} path="/instances" /> + + + +
+ ); +} diff --git a/src/components/browse/index.js b/src/components/browse/index.js index 762713edf..325139ec3 100644 --- a/src/components/browse/index.js +++ b/src/components/browse/index.js @@ -1,45 +1,35 @@ -import React, { useEffect } from 'react'; +import React, { useState } from 'react'; import { Row, Col, CardBody, Card } from '@nio/ui-kit'; import { useHistory } from 'react-router'; import { useParams } from 'react-router-dom'; +import useAsyncEffect from 'use-async-effect'; import DataTable from './datatable'; import EntityManager from '../shared/entityManager'; import JSONViewer from './jsonviewer'; import CSVUploader from './csvuploader'; +import setStructureEntities from '../../util/setStructureEntities'; +import handleSchemaTableRedirect from '../../util/handleSchemaTableRedirect'; export default ({ auth, structure, refreshInstance }) => { const history = useHistory(); const { instance_id, schema, table, action } = useParams(); + const [entities, setEntities] = useState({ schemas: [], tables: [], activeTable: false }); - const schemas = structure && Object.keys(structure).sort(); - const tables = structure && schemas && structure[schema] && Object.keys(structure[schema]).sort(); - const activeTable = structure && schemas && structure[schema] && tables && structure[schema][table] && structure[schema][table]; + useAsyncEffect(() => { + handleSchemaTableRedirect({ entities, instance_id, schema, table, history, targetPath: '/browse' }); + }, [schema, table, entities]); - useEffect(() => { - switch (true) { - case (!schemas && history.location.pathname !== '/browse'): - history.push(`/instances/${instance_id}/browse`); - break; - case (schemas && schemas.length && !schema): - case (schemas && schemas.length && schema && !schemas.includes(schema)): - history.push(`/instances/${instance_id}/browse/${schemas[0]}`); - break; - case (tables && tables.length && !table): - case (tables && tables.length && table && !tables.includes(table)): - history.push(`/instances/${instance_id}/browse/${schema}/${tables[0]}`); - break; - default: - break; - } - }, [schema, table, schemas, tables, history, instance_id]); + useAsyncEffect(() => { + if (structure) setEntities(setStructureEntities({ structure, schema, table })); + }, [structure, schema, table]); return structure ? ( { { schema && ( { )} - { schema && table && action === 'csv' && activeTable ? ( + { schema && table && action === 'csv' && entities.activeTable ? ( - ) : schema && table && action && activeTable ? ( + ) : schema && table && action && entities.activeTable ? ( - ) : schema && table && activeTable ? ( + ) : schema && table && entities.activeTable ? ( {   -
Please {(schema && tables && !tables.length) || !schemas.length ? 'create' : 'choose'} a {schema ? 'table' : 'schema'}
+
Please {(schema && entities.tables && !entities.tables.length) || !entities.schemas.length ? 'create' : 'choose'} a {schema ? 'table' : 'schema'}
diff --git a/src/components/clustering/datatable.js b/src/components/clustering/datatable.js new file mode 100644 index 000000000..bb199804e --- /dev/null +++ b/src/components/clustering/datatable.js @@ -0,0 +1,65 @@ +import React, { useEffect, useState } from 'react'; +import ReactTable from 'react-table'; +import { Card, CardBody, Col, Row } from '@nio/ui-kit'; + +import defaultTableState from '../../util/defaultTableState'; +import clusterConfigColumns from '../../util/clusterConfigColumns'; + +export default ({ instances, network, schema, table, auth, refreshInstance }) => { + const [tableState, setTableState] = useState(defaultTableState); + const [tableData, setTableData] = useState({ data: [], columns: clusterConfigColumns({ auth, refreshInstance }) }); + + useEffect(() => { + if (table) { + const newTableData = instances.map((i) => { + const connection = network && network.outbound_connections.find((n) => n.name === i.id); + const subscriptions = schema && table && connection && connection.subscriptions ? connection.subscriptions : []; + const channel = schema && table && `${schema}:${table}`; + const channelSubscription = channel && subscriptions.find((s) => s.channel === channel); + const publish = channelSubscription && channelSubscription.publish; + const subscribe = channelSubscription && channelSubscription.subscribe; + const clusterPort = 12345; + const { id, name, is_ssl, host, port } = i; + const url = `http${is_ssl ? 's' : ''}://${host}:${port}`; + return { id, name, is_ssl, host, port, url, clusterPort, publish, subscribe, connection, channel, subscriptions }; + }); + setTableData({ ...tableData, data: newTableData }); + setTableState({ ...tableState, filtered: [], sorted: [{ id: 'name', desc: false }], page: 0 }); + } + }, [table]); + + return ( + <> + + + + {schema} > {table} + + + + setTableState({ ...tableState, filtered: tableState.showFilter ? [] : tableState.filtered, showFilter: !tableState.showFilter })} /> + + + + + setTableState({ ...tableState, filtered: value })} + filtered={tableState.filtered} + onSortedChange={(value) => setTableState({ ...tableState, sorted: value })} + sorted={tableState.sorted} + onPageChange={(value) => setTableState({ ...tableState, page: value })} + page={tableState.page} + filterable={tableState.showFilter} + defaultPageSize={tableState.pageSize} + pageSize={tableState.pageSize} + onPageSizeChange={(value) => setTableState({ ...tableState, pageSize: value })} + resizable={false} + /> + + + + ); +}; diff --git a/src/components/clustering/manage.js b/src/components/clustering/manage.js index bc4a63ff1..aeb74c4da 100644 --- a/src/components/clustering/manage.js +++ b/src/components/clustering/manage.js @@ -1,14 +1,17 @@ import React, { useState } from 'react'; -import { Code, Row, Col } from '@nio/ui-kit'; +import { Row, Col } from '@nio/ui-kit'; import { useParams } from 'react-router-dom'; -import stringify from 'json-stringify-pretty-compact'; import { useHistory } from 'react-router'; import useAsyncEffect from 'use-async-effect'; import EntityManager from '../shared/entityManager'; import useLMS from '../../stores/lmsData'; import ClusterCard from './clusterCard'; +import DataTable from './datatable'; import useInstanceAuth from '../../stores/instanceAuths'; +import setStructureEntities from '../../util/setStructureEntities'; +import handleSchemaTableRedirect from '../../util/handleSchemaTableRedirect'; +import defaultTableState from '../../util/defaultTableState'; export default ({ auth, network, refreshInstance, structure }) => { const history = useHistory(); @@ -16,32 +19,15 @@ export default ({ auth, network, refreshInstance, structure }) => { const { instance_id, schema, table } = useParams(); const [instanceAuths, setInstanceAuths] = useInstanceAuth({}); const [entities, setEntities] = useState({ schemas: [], tables: [] }); + const [tableState, setTableState] = useState(defaultTableState); useAsyncEffect(() => { - if (structure) { - const newSchemas = structure && Object.keys(structure); - const newTables = structure && newSchemas && structure[schema] && Object.keys(structure[schema]); - setEntities({ schemas: newSchemas, tables: newTables }); - } - }, [structure, schema]); + handleSchemaTableRedirect({ entities, instance_id, schema, table, history, targetPath: '/clustering' }); + }, [schema, table, entities]); useAsyncEffect(() => { - switch (true) { - case (!entities.schemas && history.location.pathname !== '/clustering'): - history.push(`/instances/${instance_id}/clustering`); - break; - case (entities.schemas && entities.schemas.length && !schema): - case (entities.schemas && entities.schemas.length && schema && !entities.schemas.includes(schema)): - history.push(`/instances/${instance_id}/clustering/${entities.schemas[0]}`); - break; - case (entities.tables && entities.tables.length && !table): - case (entities.tables && entities.tables.length && table && !entities.tables.includes(table)): - history.push(`/instances/${instance_id}/clustering/${schema}/${entities.tables[0]}`); - break; - default: - break; - } - }, [schema, entities, table]); + if (structure) setEntities(setStructureEntities({ structure, schema, table })); + }, [structure, schema, table]); return ( @@ -67,6 +53,16 @@ export default ({ auth, network, refreshInstance, structure }) => { )} + i.id !== instance_id)} + network={network} + schema={schema} + table={table} + auth={auth} + instance_id={instance_id} + refreshInstance={refreshInstance} + /> + {/*
{schema} > {table}
@@ -76,7 +72,7 @@ export default ({ auth, network, refreshInstance, structure }) => { key={i.id} {...i} connection={network && network.outbound_connections.find((n) => n.name === i.id)} - clusterPort={12345} /* TODO: use a new getter to get this value */ + clusterPort={12345} schema={schema} table={table} refreshInstance={refreshInstance} @@ -86,7 +82,7 @@ export default ({ auth, network, refreshInstance, structure }) => { /> ))}
- {/* +
{stringify(network.outbound_connections, { maxLength: 20 })} diff --git a/src/components/instances.js b/src/components/instances.js index 77f93c40d..08f5654c4 100644 --- a/src/components/instances.js +++ b/src/components/instances.js @@ -2,33 +2,27 @@ import React from 'react'; import { Row } from '@nio/ui-kit'; import TopNav from './topnav'; -import useLMS from '../stores/lmsData'; import useInstanceAuth from '../stores/instanceAuths'; import InstanceCard from './instances/instanceCard'; import NewInstanceCard from './instances/newInstanceCard'; -export default () => { - const [lmsData] = useLMS({ auth: false, instances: [] }); +export default ({ lmsData }) => { const [instanceAuths, setInstanceAuths] = useInstanceAuth({}); return ( <> - {lmsData.instances ? ( - - - {lmsData.instances.map((i) => ( - setInstanceAuths({ ...instanceAuths, [id]: user && pass ? { user, pass } : false })} - /> - ))} - - ) : ( - - )} + + + {lmsData.instances.map((i) => ( + setInstanceAuths({ ...instanceAuths, [id]: user && pass ? { user, pass } : false })} + /> + ))} + ); }; diff --git a/src/components/instances/cardBack.js b/src/components/instances/cardBack.js index bb80e1b8a..343c9b459 100644 --- a/src/components/instances/cardBack.js +++ b/src/components/instances/cardBack.js @@ -3,7 +3,7 @@ import { Button, Card, CardBody, Input, Row, Col } from '@nio/ui-kit'; import useAsyncEffect from 'use-async-effect'; import { useAlert } from 'react-alert'; import queryInstance from '../../util/queryInstance'; -import defaultFormData from '../../util/defaultInstanceFormData'; +import defaultFormData from '../../util/defaultAuthFormData'; export default ({ id, host, port, is_ssl, setAuth, flipCard, flipState }) => { const alert = useAlert(); diff --git a/src/components/instances/cloudInstanceForm.js b/src/components/instances/cloudInstanceForm.js new file mode 100644 index 000000000..1d733f469 --- /dev/null +++ b/src/components/instances/cloudInstanceForm.js @@ -0,0 +1,117 @@ +import React, { useState } from 'react'; +import { Col, Input, Row, RadioCheckbox, Button } from '@nio/ui-kit'; +import { useAlert } from 'react-alert'; +import useAsyncEffect from 'use-async-effect'; + +import defaultInstanceFormData from '../../util/defaultInstanceFormData'; +import useLMS from '../../stores/lmsData'; +import defaultLMSData from '../../util/defaultLMSData'; +import queryLMS from '../../util/queryLMS'; + +export default ({ instanceType, products, regions, setShowForm }) => { + const alert = useAlert(); + const [lmsData] = useLMS(defaultLMSData); + const [formData, updateForm] = useState(defaultInstanceFormData); + + useAsyncEffect(async () => { + if (formData.submitted) { + formData.is_local = instanceType === 'local'; + + console.log(formData); + return false; + + const newInstance = await queryLMS({ + endpoint: 'addInstance', + method: 'POST', + payload: formData, + auth: lmsData.auth, + }); + if (newInstance.error) { + alert.error(newInstance.error); + updateForm({ ...formData, error: newInstance.error, submitted: false }); + } else { + updateForm(defaultInstanceFormData); + } + } + }, [formData]); + + return ( +
+
Instance Type
+
+ updateForm({ ...formData, stripe_product_id: value })} + options={products} + defaultValue={products[0]} + /> +
+ +
Instance Region (scroll for more)
+
+ updateForm({ ...formData, instance_region: value })} + options={regions} + defaultValue={regions[0]} + /> +
+ +
Admin Credentials
+
+ + + Username + + + updateForm({ ...formData, user: e.target.value, error: false })} + className="text-center" + type="text" + title="username" + /> + + +
+ + + Password + + + updateForm({ ...formData, pass: e.target.value, error: false })} + className="text-center" + type="password" + title="password" + /> + + +
+
+ + + + + + + + +
+ ); +}; diff --git a/src/components/instances/localInstanceForm.js b/src/components/instances/localInstanceForm.js new file mode 100644 index 000000000..d46955c49 --- /dev/null +++ b/src/components/instances/localInstanceForm.js @@ -0,0 +1,150 @@ +import React, { useState } from 'react'; +import { Col, Input, Row, RadioCheckbox, Button } from '@nio/ui-kit'; +import { useAlert } from 'react-alert'; +import useAsyncEffect from 'use-async-effect'; + +import defaultInstanceFormData from '../../util/defaultInstanceFormData'; +import useLMS from '../../stores/lmsData'; +import defaultLMSData from '../../util/defaultLMSData'; +import queryLMS from '../../util/queryLMS'; + +export default ({ instanceType, products, setShowForm }) => { + const [lmsData] = useLMS(defaultLMSData); + const alert = useAlert(); + const [formData, updateForm] = useState(defaultInstanceFormData); + + useAsyncEffect(async () => { + if (formData.submitted) { + formData.is_local = instanceType === 'local'; + + console.log(formData); + return false; + + const newInstance = await queryLMS({ + endpoint: 'addInstance', + method: 'POST', + payload: formData, + auth: lmsData.auth, + }); + if (newInstance.error) { + alert.error(newInstance.error); + updateForm({ ...formData, error: newInstance.error, submitted: false }); + } else { + updateForm(defaultInstanceFormData); + } + } + }, [formData]); + + return ( +
+
Instance Type
+
+ console.log(value)} + options={products} + defaultValue={products[0]} + /> +
+ +
Instance Details
+
+ + + Host + + + updateForm({ ...formData, host: e.target.value, error: false })} + className="text-center" + type="text" + title="host" + /> + + +
+ + + Port + + + updateForm({ ...formData, port: e.target.value, error: false })} + className="text-center" + type="number" + title="port" + /> + + +
+ + + SSL + + + updateForm({ ...formData, is_ssl: value || false, error: false })} + options={{ label: '', value: true }} + /> + + +
+ +
Admin Credentials
+
+ + + Username + + + updateForm({ ...formData, user: e.target.value, error: false })} + className="text-center" + type="text" + title="username" + /> + + +
+ + + Password + + + updateForm({ ...formData, pass: e.target.value, error: false })} + className="text-center" + type="password" + title="password" + /> + + +
+
+ + + + + + + + +
+ ); +}; diff --git a/src/components/instances/newInstanceCard.js b/src/components/instances/newInstanceCard.js index c2ff8d075..8894b3688 100644 --- a/src/components/instances/newInstanceCard.js +++ b/src/components/instances/newInstanceCard.js @@ -1,88 +1,21 @@ import React, { useState } from 'react'; -import { Button, Card, CardBody, Col, Input, Row } from '@nio/ui-kit'; -import { useAlert } from 'react-alert'; -import useAsyncEffect from 'use-async-effect'; +import { Card, CardBody, Col } from '@nio/ui-kit'; -import defaultFormData from '../../util/defaultInstanceFormData'; -import useLMS from '../../stores/lmsData'; -import defaultLMSData from '../../util/defaultLMSData'; -import queryLMS from '../../util/queryLMS'; +import NewInstanceModal from './newInstanceModal'; export default () => { const [showForm, setShowForm] = useState(false); - const [lmsData] = useLMS(defaultLMSData); - const alert = useAlert(); - const [formData, updateForm] = useState(defaultFormData); - - useAsyncEffect(async () => { - if (formData.submitted) { - const newInstance = await queryLMS({ - endpoint: 'addInstance', - method: 'POST', - payload: formData, - auth: lmsData.auth, - }); - if (newInstance.error) { - alert.error(newInstance.error); - updateForm({ ...formData, error: newInstance.error, submitted: false }); - } else { - updateForm(defaultFormData); - } - } - }, [formData]); return ( - - {showForm ? ( - - - updateForm({ ...formData, user: e.target.value, error: false })} - className="text-center mb-2" - type="text" - title="username" - placeholder="user" - bsSize="sm" - /> - updateForm({ ...formData, pass: e.target.value, error: false })} - className="text-center mb-2" - type="password" - title="password" - placeholder="pass" - bsSize="sm" - /> - - - - - - - - - - - ) : ( + <> + setShowForm(true)}> - )} - + + + ); }; diff --git a/src/components/instances/newInstanceModal.js b/src/components/instances/newInstanceModal.js new file mode 100644 index 000000000..52d6ff458 --- /dev/null +++ b/src/components/instances/newInstanceModal.js @@ -0,0 +1,62 @@ +import React, { useState } from 'react'; +import { Button, Modal, ModalHeader, ModalBody, ButtonGroup } from '@nio/ui-kit'; +import useAsyncEffect from 'use-async-effect'; + +import useLMS from '../../stores/lmsData'; +import defaultLMSData from '../../util/defaultLMSData'; +import queryLMS from '../../util/queryLMS'; +import LocalInstanceForm from './localInstanceForm'; +import CloudInstanceForm from './cloudInstanceForm'; + +export default ({ showForm, setShowForm }) => { + const [lmsData] = useLMS(defaultLMSData); + const [instanceType, setInstanceType] = useState('cloud'); + const [products, setProducts] = useState(false); + const [regions, setRegions] = useState(false); + + useAsyncEffect(async () => { + if (showForm) { + const newProducts = await queryLMS({ + endpoint: 'getProducts', + method: 'POST', + auth: lmsData.auth, + }); + setProducts(newProducts); + const newRegions = await queryLMS({ + endpoint: 'getRegions', + method: 'POST', + auth: lmsData.auth, + }); + setRegions(newRegions); + } + }, [showForm]); + + return ( + setShowForm(false)}> + setShowForm(false)}> + Add New Instance + + + + + + + + {products && regions && instanceType === 'local' ? ( + ({ label: `${p.instance_ram}GB RAM | ${p.instance_disk_space_gigs}GB Disk Space | $${p.local_price_annual}/year`, value: p.stripe_product_id }))} + setShowForm={setShowForm} + /> + ) : products && regions ? ( + ({ label: `${p.instance_ram}GB RAM | ${p.instance_disk_space_gigs}GB Disk Space | $${p.cloud_price_monthly}/month`, value: p.stripe_product_id }))} + regions={regions} + setShowForm={setShowForm} + /> + ) : null} + + + ); +}; diff --git a/src/components/login.js b/src/components/login.js index d7919ecc0..a77c046d8 100644 --- a/src/components/login.js +++ b/src/components/login.js @@ -5,7 +5,7 @@ import { useHistory } from 'react-router'; import useLMS from '../stores/lmsData'; import queryLMS from '../util/queryLMS'; -import defaultFormData from '../util/defaultInstanceFormData'; +import defaultFormData from '../util/defaultAuthFormData'; import defaultLMSData from '../util/defaultLMSData'; export default () => { @@ -23,7 +23,7 @@ export default () => { const instances = await queryLMS({ endpoint: 'getInstances', method: 'GET', - auth: lmsData.auth, + auth: { user: formData.user, pass: formData.pass }, }); if (instances.error) { updateForm({ ...formData, error: instances.error, submitted: false }); @@ -37,7 +37,7 @@ export default () => { }, [formData]); useAsyncEffect(() => { - if (lmsData.auth.user && lmsData.auth.pass && !formData.submitted) { + if (lmsData?.auth?.user && lmsData?.auth?.pass && !formData.submitted) { updateForm({ user: lmsData.auth.user, pass: lmsData.auth.pass, submitted: true, error: false }); } }, [lmsData]); diff --git a/src/index.html b/src/index.html index 39bfdba21..307e553aa 100644 --- a/src/index.html +++ b/src/index.html @@ -9,5 +9,6 @@
+
diff --git a/src/index.js b/src/index.js index ae32e72a3..e3efe4822 100644 --- a/src/index.js +++ b/src/index.js @@ -16,7 +16,6 @@ render( -
), document.getElementById('app'), diff --git a/src/util/clusterConfigColumns.js b/src/util/clusterConfigColumns.js new file mode 100644 index 000000000..ed8960349 --- /dev/null +++ b/src/util/clusterConfigColumns.js @@ -0,0 +1,36 @@ +import React from 'react'; +import { Button, ToggleButton } from '@nio/ui-kit'; + +import connectToInstance from './connectToInstance'; +import updateInstanceSubscription from './updateInstanceSubscription'; + +export default ({ auth, refreshInstance }) => [{ + Header: 'name', + accessor: 'name', + style: { paddingTop: 10 }, +}, { + Header: 'url', + accessor: 'url', + style: { paddingTop: 10 }, +}, { + Header: 'publish', + Cell: ({ + original: { id, host, port, clusterPort, publish, connection, channel, subscriptions } + }) => ( + connection + ? updateInstanceSubscription({ channel, subscriptions, buttonState: 'togglePublish', id, host, clusterPort, auth, refreshInstance })} /> + : ), + width: 79, + style: { paddingTop: 2, paddingBottom: 2, paddingLeft: 5, paddingRight: 5 }, +}, { + Header: 'subscribe', + Cell: ({ + original: { id, host, clusterPort, subscribe, connection, channel, subscriptions } + }) => ( + connection + ? updateInstanceSubscription({ channel, subscriptions, buttonState: 'toggleSubscribe', id, host, clusterPort, auth, refreshInstance })} /> + : '' + ), + width: 79, + style: { paddingTop: 2, paddingBottom: 2, paddingLeft: 5, paddingRight: 5 }, +}]; diff --git a/src/util/connectToInstance.js b/src/util/connectToInstance.js index b51fe02a4..1ff6de3e7 100644 --- a/src/util/connectToInstance.js +++ b/src/util/connectToInstance.js @@ -1,6 +1,7 @@ import queryInstance from './queryInstance'; -export default async ({ id, host, clusterPort, auth }) => { +export default async ({ id, host, clusterPort, auth, refreshInstance }) => { await queryInstance({ operation: 'remove_node', name: id }, auth); - return queryInstance({ operation: 'add_node', name: id, host, port: clusterPort, subscriptions: [] }, auth); + await queryInstance({ operation: 'add_node', name: id, host, port: clusterPort, subscriptions: [] }, auth); + return setTimeout(() => refreshInstance(Date.now()), 100); }; diff --git a/src/util/defaultAuthFormData.js b/src/util/defaultAuthFormData.js new file mode 100644 index 000000000..8e1ebe214 --- /dev/null +++ b/src/util/defaultAuthFormData.js @@ -0,0 +1 @@ +export default { submitted: false, error: false, user: false, pass: false }; diff --git a/src/util/defaultInstanceFormData.js b/src/util/defaultInstanceFormData.js index 8e1ebe214..869c8c883 100644 --- a/src/util/defaultInstanceFormData.js +++ b/src/util/defaultInstanceFormData.js @@ -1 +1,11 @@ -export default { submitted: false, error: false, user: false, pass: false }; +export default { + submitted: false, + error: false, + user: false, + pass: false, + stripe_product_id: false, + instance_name: false, + instance_region: false, + is_ssl: false, + is_local: false, +}; diff --git a/src/util/getInstanceDataStructure.js b/src/util/getInstanceDataStructure.js index 2a4a2a9d0..f935b14d2 100644 --- a/src/util/getInstanceDataStructure.js +++ b/src/util/getInstanceDataStructure.js @@ -16,6 +16,7 @@ export default (dbResponse) => { dataTableColumns: orderedColumns.map((k) => ({ Header: k.replace(/__/g, ''), accessor: k, + style: { height: 29, paddingTop: 10 }, Cell: (props) => handleCellValues(props.value), })), }; diff --git a/src/util/handleSchemaTableRedirect.js b/src/util/handleSchemaTableRedirect.js new file mode 100644 index 000000000..afbce4c7c --- /dev/null +++ b/src/util/handleSchemaTableRedirect.js @@ -0,0 +1,17 @@ +export default ({ entities, instance_id, schema, table, history, targetPath }) => { + switch (true) { + case (!entities.schemas && history.location.pathname !== targetPath): + history.push(`/instances/${instance_id}${targetPath}`); + break; + case (entities.schemas && entities.schemas.length && !schema): + case (entities.schemas && entities.schemas.length && schema && !entities.schemas.includes(schema)): + history.push(`/instances/${instance_id}${targetPath}/${entities.schemas[0]}`); + break; + case (entities.tables && entities.tables.length && !table): + case (entities.tables && entities.tables.length && table && !entities.tables.includes(table)): + history.push(`/instances/${instance_id}${targetPath}/${schema}/${entities.tables[0]}`); + break; + default: + break; + } +} diff --git a/src/util/queryLMS.js b/src/util/queryLMS.js index 2013d1db5..aa49bb155 100644 --- a/src/util/queryLMS.js +++ b/src/util/queryLMS.js @@ -1,12 +1,14 @@ import instances from '../../mock_data/LMS_API.instances.json'; +import products from '../../mock_data/LMS_API.products.json'; +import regions from '../../mock_data/LMS_API.aws_regions.json'; // eslint-disable-next-line no-unused-vars -export default async ({ endpoint, method, payload, auth }) => { +export default async ({ endpoint, payload, auth }) => { /* const request = await fetch( `https://api.harperdb.io/v1/${endpoint}`, { - method, + method: 'POST', body: JSON.stringify(payload), headers: { 'Content-Type': 'application/json', @@ -20,6 +22,10 @@ export default async ({ endpoint, method, payload, auth }) => { switch (endpoint) { case 'getInstances': return instances; + case 'getProducts': + return products; + case 'getRegions': + return regions; default: return { error: 'unknown endpoint' }; } diff --git a/src/util/setStructureEntities.js b/src/util/setStructureEntities.js new file mode 100644 index 000000000..82fad9af0 --- /dev/null +++ b/src/util/setStructureEntities.js @@ -0,0 +1,6 @@ +export default ({ structure, schema, table }) => { + const schemas = structure && Object.keys(structure).sort(); + const tables = structure && schemas && structure[schema] && Object.keys(structure[schema]).sort(); + const activeTable = structure && schemas && structure[schema] && tables && structure[schema][table] && structure[schema][table]; + return { schemas, tables, activeTable }; +}; diff --git a/src/util/updateInstanceSubscription.js b/src/util/updateInstanceSubscription.js index 789a81618..21dd19c21 100644 --- a/src/util/updateInstanceSubscription.js +++ b/src/util/updateInstanceSubscription.js @@ -1,6 +1,6 @@ import queryInstance from './queryInstance'; -export default async ({ channel, subscriptions, buttonState, id, host, clusterPort, auth }) => { +export default async ({ channel, subscriptions, buttonState, id, host, clusterPort, auth, refreshInstance }) => { const existingChannelSubscriptionIndex = subscriptions.findIndex((s) => s.channel === channel); // if we have no subscription for this node, add it to the subscriptions array @@ -27,5 +27,6 @@ export default async ({ channel, subscriptions, buttonState, id, host, clusterPo // send the query const operation = { operation: 'update_node', name: id, host, port: clusterPort, subscriptions }; - return queryInstance(operation, auth); + await queryInstance(operation, auth); + return setTimeout(() => refreshInstance(Date.now()), 100); }; From f8a90be13e5d13e5405cf8cea098f834331f61ce Mon Sep 17 00:00:00 2001 From: deliciousmonster Date: Wed, 19 Feb 2020 18:40:48 -0700 Subject: [PATCH 10/19] instance_name --- src/components/clustering/datatable.js | 4 ++-- src/components/instances/cardFront.js | 4 ++-- src/components/topnav.js | 2 +- src/util/clusterConfigColumns.js | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/clustering/datatable.js b/src/components/clustering/datatable.js index bb199804e..e6c2ab971 100644 --- a/src/components/clustering/datatable.js +++ b/src/components/clustering/datatable.js @@ -19,9 +19,9 @@ export default ({ instances, network, schema, table, auth, refreshInstance }) => const publish = channelSubscription && channelSubscription.publish; const subscribe = channelSubscription && channelSubscription.subscribe; const clusterPort = 12345; - const { id, name, is_ssl, host, port } = i; + const { id, instance_name, is_ssl, host, port } = i; const url = `http${is_ssl ? 's' : ''}://${host}:${port}`; - return { id, name, is_ssl, host, port, url, clusterPort, publish, subscribe, connection, channel, subscriptions }; + return { id, instance_name, is_ssl, host, port, url, clusterPort, publish, subscribe, connection, channel, subscriptions }; }); setTableData({ ...tableData, data: newTableData }); setTableState({ ...tableState, filtered: [], sorted: [{ id: 'name', desc: false }], page: 0 }); diff --git a/src/components/instances/cardFront.js b/src/components/instances/cardFront.js index 6239d711a..bc43ec5bd 100644 --- a/src/components/instances/cardFront.js +++ b/src/components/instances/cardFront.js @@ -3,7 +3,7 @@ import { Card, CardBody, Col, Row } from '@nio/ui-kit'; import { useHistory } from 'react-router'; import { useAlert } from 'react-alert'; -export default ({ id, name, host, port, is_ssl, flipCard, setAuth, hasAuth }) => { +export default ({ id, instance_name, host, port, is_ssl, flipCard, setAuth, hasAuth }) => { const history = useHistory(); const alert = useAlert(); @@ -12,7 +12,7 @@ export default ({ id, name, host, port, is_ssl, flipCard, setAuth, hasAuth }) => - {name} + {instance_name} {hasAuth ? ( diff --git a/src/components/topnav.js b/src/components/topnav.js index 9694cad21..4fcf57848 100644 --- a/src/components/topnav.js +++ b/src/components/topnav.js @@ -30,7 +30,7 @@ export default ({ routes = false }) => { > - {thisInstance && thisInstance.name} + {thisInstance && thisInstance.instance_name} > diff --git a/src/util/clusterConfigColumns.js b/src/util/clusterConfigColumns.js index ed8960349..e3d4b2287 100644 --- a/src/util/clusterConfigColumns.js +++ b/src/util/clusterConfigColumns.js @@ -6,7 +6,7 @@ import updateInstanceSubscription from './updateInstanceSubscription'; export default ({ auth, refreshInstance }) => [{ Header: 'name', - accessor: 'name', + accessor: 'instance_name', style: { paddingTop: 10 }, }, { Header: 'url', From 86de8747589a76d8d002b7e8303626ce18510082 Mon Sep 17 00:00:00 2001 From: deliciousmonster Date: Fri, 21 Feb 2020 16:56:32 -0700 Subject: [PATCH 11/19] payment --- mock_data/LMS_API.customer.json | 47 ++ mock_data/LMS_API.user.json | 7 + package-lock.json | 458 ++++-------------- package.json | 6 +- src/assets/styles/_forms.scss | 11 +- src/assets/styles/_modal.scss | 19 +- src/assets/styles/_typography.scss | 2 +- src/components/app.js | 25 +- src/components/browse/datatable.js | 2 +- src/components/clustering/clusterCard.js | 72 --- src/components/clustering/datatable.js | 2 +- src/components/clustering/manage.js | 32 -- src/components/instances.js | 14 +- src/components/instances/cloudInstanceForm.js | 187 ++++--- src/components/instances/confirmOrderForm.js | 95 ++++ .../instances/customerPaymentForm.js | 55 +++ src/components/instances/instanceTypeForm.js | 43 ++ src/components/instances/localInstanceForm.js | 247 +++++----- src/components/instances/newInstanceCard.js | 2 +- src/components/instances/newInstanceModal.js | 119 +++-- src/components/login.js | 12 +- src/index.js | 18 +- src/util/addInstance.js | 8 + src/util/cardOptions.js | 23 + src/util/clusterConfigColumns.js | 18 +- src/util/customerHasChargeableCard.js | 1 + src/util/defaultInstanceFormData.js | 8 +- src/util/defaultLMSData.js | 2 +- src/util/getCustomer.js | 7 + src/util/getInstancePrice.js | 4 + src/util/getInstances.js | 7 + src/util/getProductsAndRegions.js | 20 + src/util/queryLMS.js | 10 + src/util/stripePromise.js | 5 + src/util/textDecoderPolyfill.js | 3 + webpack.common.js | 6 +- 36 files changed, 804 insertions(+), 793 deletions(-) create mode 100644 mock_data/LMS_API.customer.json create mode 100644 mock_data/LMS_API.user.json delete mode 100644 src/components/clustering/clusterCard.js create mode 100644 src/components/instances/confirmOrderForm.js create mode 100644 src/components/instances/customerPaymentForm.js create mode 100644 src/components/instances/instanceTypeForm.js create mode 100644 src/util/addInstance.js create mode 100644 src/util/cardOptions.js create mode 100644 src/util/customerHasChargeableCard.js create mode 100644 src/util/getCustomer.js create mode 100644 src/util/getInstancePrice.js create mode 100644 src/util/getInstances.js create mode 100644 src/util/getProductsAndRegions.js create mode 100644 src/util/stripePromise.js create mode 100644 src/util/textDecoderPolyfill.js diff --git a/mock_data/LMS_API.customer.json b/mock_data/LMS_API.customer.json new file mode 100644 index 000000000..95d4c92d8 --- /dev/null +++ b/mock_data/LMS_API.customer.json @@ -0,0 +1,47 @@ +{ + "id": "guid", + "name": "customer name", + "customer_hubspot_id": "guid", + "stripe_customer_object": { + "balance": 0, + "delinquent": true, + "discount": {}, + "id": "string", + "sources": { + "data": [] + }, + "subscriptions": { + "data": [ + { + "billing_cycle_anchor": 0, + "billing_thresholds": {}, + "cancel_at": 0, + "cancel_at_period_end": true, + "canceled_at": 0, + "created": 0, + "current_period_end": 0, + "current_period_start": 0, + "customer": "string", + "default_payment_method": "string", + "default_source": "string", + "discount": {}, + "ended_at": 0, + "id": "string", + "items": { + "data": [ + {} + ] + }, + "latest_invoice": {}, + "metadata": {}, + "next_pending_invoice_item_invoice": 0, + "plan": "string", + "quantity": 0, + "schedule": {}, + "start_date": 0, + "status": "active" + } + ] + } + } +} diff --git a/mock_data/LMS_API.user.json b/mock_data/LMS_API.user.json new file mode 100644 index 000000000..fbbcda93f --- /dev/null +++ b/mock_data/LMS_API.user.json @@ -0,0 +1,7 @@ +{ + "id": "guid", + "firstname": "Jaxon", + "lastname": "Repp", + "email": "jaxon@harperdb.io", + "customer_id": "guid" +} diff --git a/package-lock.json b/package-lock.json index 5e719f61f..0441555ad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2590,31 +2590,28 @@ } }, "@nio/ui-kit": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@nio/ui-kit/-/ui-kit-2.1.2.tgz", - "integrity": "sha512-uglIDEjGU4fV3LZOEU7Gzucth4AXSSFZldUlAfWMPYK7pTN1x4oU39UHZLDrQEAl7m52Btk19FQVchE92JDuLw==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@nio/ui-kit/-/ui-kit-2.2.2.tgz", + "integrity": "sha512-PgpxCmi0v95S5xQdp4yRXqNTs1cgT96bwZZd80HZncJdBvV0F0CokrSzaUPXyUfweHxwHNGa/0ouO5SvYlR6iw==", "requires": { "@nio/icons": "^1.10.0", - "apexcharts": "^3.11.1", + "apexcharts": "^3.15.6", "bootstrap": "4.4.1", - "highlight.js": "^9.17.1", - "node-sass": "^4.13.0", - "node-sass-import": "^2.0.1", + "highlight.js": "^9.18.1", + "node-sass": "^4.13.1", "node-sass-magic-importer": "^5.3.2", - "prop-types": "^15.7.2", + "prop-types": "*", "react": "^16.12.0", - "react-apexcharts": "^1.3.3", + "react-apexcharts": "^1.3.6", "react-clock": "^2.4.0", - "react-css-themr": "^2.1.2", "react-dom": "^16.12.0", - "react-motion": "^0.5.2", "react-rangeslider": "^2.2.0", - "react-toggle-button": "^2.2.0", - "reactstrap": "^8.2.0" + "react-toggle": "^4.1.1", + "reactstrap": "^8.4.1" }, "dependencies": { "@babel/runtime": { - "version": "7.7.7", + "version": "7.8.4", "bundled": true, "requires": { "regenerator-runtime": "^0.13.2" @@ -2625,10 +2622,9 @@ "bundled": true }, "apexcharts": { - "version": "3.11.1", + "version": "3.15.6", "bundled": true, "requires": { - "promise-polyfill": "8.1.0", "svg.draggable.js": "^2.2.2", "svg.easing.js": "^2.0.0", "svg.filter.js": "^2.0.2", @@ -2687,22 +2683,6 @@ "version": "1.0.0", "bundled": true }, - "handlebars": { - "version": "4.5.3", - "bundled": true, - "requires": { - "neo-async": "^2.6.0", - "optimist": "^0.6.1", - "source-map": "^0.6.1", - "uglify-js": "^3.1.4" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "bundled": true - } - } - }, "has": { "version": "1.0.3", "bundled": true, @@ -2715,23 +2695,9 @@ "bundled": true }, "highlight.js": { - "version": "9.17.1", - "bundled": true, - "requires": { - "handlebars": "^4.5.3" - } - }, - "hoist-non-react-statics": { - "version": "1.2.0", + "version": "9.18.1", "bundled": true }, - "invariant": { - "version": "2.2.2", - "bundled": true, - "requires": { - "loose-envify": "^1.0.0" - } - }, "is-arguments": { "version": "1.0.4", "bundled": true @@ -2773,14 +2739,6 @@ "version": "1.2.0", "bundled": true }, - "minimist": { - "version": "0.0.8", - "bundled": true - }, - "neo-async": { - "version": "2.6.1", - "bundled": true - }, "object-assign": { "version": "4.1.1", "bundled": true @@ -2807,30 +2765,8 @@ "object-keys": "^1.0.11" } }, - "optimist": { - "version": "0.6.1", - "bundled": true, - "requires": { - "minimist": "~0.0.1", - "wordwrap": "~0.0.2" - }, - "dependencies": { - "wordwrap": { - "version": "0.0.3", - "bundled": true - } - } - }, - "performance-now": { - "version": "0.2.0", - "bundled": true - }, "popper.js": { - "version": "1.16.0", - "bundled": true - }, - "promise-polyfill": { - "version": "8.1.0", + "version": "1.16.1", "bundled": true }, "prop-types": { @@ -2851,19 +2787,6 @@ } } }, - "raf": { - "version": "3.4.0", - "bundled": true, - "requires": { - "performance-now": "^2.1.0" - }, - "dependencies": { - "performance-now": { - "version": "2.1.0", - "bundled": true - } - } - }, "react": { "version": "16.12.0", "bundled": true, @@ -2874,7 +2797,7 @@ } }, "react-apexcharts": { - "version": "1.3.3", + "version": "1.3.6", "bundled": true, "requires": { "prop-types": "^15.5.7" @@ -2888,14 +2811,6 @@ "prop-types": "^15.6.0" } }, - "react-css-themr": { - "version": "2.1.2", - "bundled": true, - "requires": { - "hoist-non-react-statics": "^1.2.0", - "invariant": "^2.2.1" - } - }, "react-dom": { "version": "16.12.0", "bundled": true, @@ -2914,15 +2829,6 @@ "version": "3.0.4", "bundled": true }, - "react-motion": { - "version": "0.5.2", - "bundled": true, - "requires": { - "performance-now": "^0.2.0", - "prop-types": "^15.5.8", - "raf": "^3.1.0" - } - }, "react-popper": { "version": "1.3.7", "bundled": true, @@ -2944,12 +2850,11 @@ "resize-observer-polyfill": "^1.4.2" } }, - "react-toggle-button": { - "version": "2.2.0", + "react-toggle": { + "version": "4.1.1", "bundled": true, "requires": { - "prop-types": "^15.6.0", - "react-motion": "^0.5.2" + "classnames": "^2.2.5" } }, "react-transition-group": { @@ -2972,14 +2877,14 @@ } }, "reactstrap": { - "version": "8.2.0", + "version": "8.4.1", "bundled": true, "requires": { "@babel/runtime": "^7.2.0", "classnames": "^2.2.3", "prop-types": "^15.5.8", "react-lifecycles-compat": "^3.0.4", - "react-popper": "^1.3.3", + "react-popper": "^1.3.6", "react-transition-group": "^2.3.1" } }, @@ -2996,20 +2901,20 @@ }, "dependencies": { "es-abstract": { - "version": "1.17.0-next.1", + "version": "1.17.4", "bundled": true, "requires": { "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", "has": "^1.0.3", "has-symbols": "^1.0.1", - "is-callable": "^1.1.4", - "is-regex": "^1.0.4", + "is-callable": "^1.1.5", + "is-regex": "^1.0.5", "object-inspect": "^1.7.0", "object-keys": "^1.1.1", "object.assign": "^4.1.0", - "string.prototype.trimleft": "^2.1.0", - "string.prototype.trimright": "^2.1.0" + "string.prototype.trimleft": "^2.1.1", + "string.prototype.trimright": "^2.1.1" } }, "es-to-primitive": { @@ -3024,6 +2929,17 @@ "has-symbols": { "version": "1.0.1", "bundled": true + }, + "is-callable": { + "version": "1.1.5", + "bundled": true + }, + "is-regex": { + "version": "1.0.5", + "bundled": true, + "requires": { + "has": "^1.0.3" + } } } }, @@ -3106,27 +3022,6 @@ "version": "0.0.7", "bundled": true }, - "uglify-js": { - "version": "3.7.2", - "bundled": true, - "optional": true, - "requires": { - "commander": "~2.20.3", - "source-map": "~0.6.1" - }, - "dependencies": { - "commander": { - "version": "2.20.3", - "bundled": true, - "optional": true - }, - "source-map": { - "version": "0.6.1", - "bundled": true, - "optional": true - } - } - }, "warning": { "version": "4.0.3", "bundled": true, @@ -3141,6 +3036,19 @@ "resolved": "https://registry.npmjs.org/@sheerun/mutationobserver-shim/-/mutationobserver-shim-0.3.2.tgz", "integrity": "sha512-vTCdPp/T/Q3oSqwHmZ5Kpa9oI7iLtGl3RQaA/NyLHikvcrPxACkkKVr/XzkSPJWXHRhKGzVvb0urJsbMlRxi1Q==" }, + "@stripe/react-stripe-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@stripe/react-stripe-js/-/react-stripe-js-1.0.2.tgz", + "integrity": "sha512-xqPtVc6OLWJifWzEfn7hScYn9na0XHjtjKXNfqatqSbL+rxJ0JGAUyUYpRXGDdw/9DlLLZCYjIpgyxiqwnXq5g==", + "requires": { + "prop-types": "^15.7.2" + } + }, + "@stripe/stripe-js": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-1.0.1.tgz", + "integrity": "sha512-R6G3nBeMpT62+itLyn0uLIiDF3jqe7PqbCwGJtPYN5JBVczJGU9ZkOqICwfUQTSwIiL7kom9FgIa8vmO4MpwQQ==" + }, "@testing-library/dom": { "version": "6.11.0", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-6.11.0.tgz", @@ -3996,14 +3904,6 @@ "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", "dev": true }, - "async": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.0.tgz", - "integrity": "sha512-xAfGg1/NTLBBKlHFmnd7PlmUW9KhVQIUuSrYem9xzFUZy13ScvtyGGejaae9iAVRiRq9+Cx7DPFaAAhCpyxyPw==", - "requires": { - "lodash": "^4.14.0" - } - }, "async-each": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", @@ -7080,32 +6980,6 @@ "safe-buffer": "^5.1.1" } }, - "execa": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", - "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", - "requires": { - "cross-spawn": "^5.0.1", - "get-stream": "^3.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" - }, - "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=", - "requires": { - "lru-cache": "^4.0.1", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - } - } - }, "exit-hook": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz", @@ -7382,15 +7256,41 @@ } }, "file-loader": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-5.0.2.tgz", - "integrity": "sha512-QMiQ+WBkGLejKe81HU8SZ9PovsU/5uaLo0JdTCEXOYv7i7jfAjHZi1tcwp9tSASJPOmmHZtbdCervFmXMH/Dcg==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-5.1.0.tgz", + "integrity": "sha512-u/VkLGskw3Ue59nyOwUwXI/6nuBCo7KBkniB/l7ICwr/7cPNGsL1WCXUp3GB0qgOOKU1TiP49bv4DZF/LJqprg==", "dev": true, "requires": { - "loader-utils": "^1.2.3", + "loader-utils": "^1.4.0", "schema-utils": "^2.5.0" }, "dependencies": { + "emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true + }, + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + } + }, "schema-utils": { "version": "2.6.4", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.4.tgz", @@ -8286,11 +8186,6 @@ "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=" }, - "get-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" - }, "get-value": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", @@ -9472,11 +9367,6 @@ "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", "dev": true }, - "is-object": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.1.tgz", - "integrity": "sha1-iVJojF7C/9awPsyF52ngKQMINHA=" - }, "is-path-cwd": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", @@ -9545,12 +9435,14 @@ "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true }, "is-string": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", - "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==" + "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==", + "dev": true }, "is-svg": { "version": "3.0.0", @@ -9969,6 +9861,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, "requires": { "p-locate": "^2.0.0", "path-exists": "^3.0.0" @@ -9977,7 +9870,8 @@ "path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true } } }, @@ -10129,14 +10023,6 @@ "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", "dev": true }, - "mem": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz", - "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", - "requires": { - "mimic-fn": "^1.0.0" - } - }, "memory-fs": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", @@ -10237,11 +10123,6 @@ "mime-db": "1.43.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==" - }, "min-indent": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.0.tgz", @@ -10633,139 +10514,6 @@ "true-case-path": "^1.0.2" } }, - "node-sass-import": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/node-sass-import/-/node-sass-import-2.0.1.tgz", - "integrity": "sha512-V0G3+bcVpLmOLD2tqUGy5DRi1FKpDIaJBuFCEYn/MmFp6EoJGnw4duM5419af0Xg4gFAOSFZPXX8MXEHpmFlGQ==", - "requires": { - "async": "2.6.0", - "glob": "7.1.2", - "path-format": "1.2.1", - "path-parse": "1.0.5", - "resolve": "1.5.0", - "yargs": "11.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=" - }, - "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=" - }, - "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" - } - }, - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "requires": { - "locate-path": "^2.0.0" - } - }, - "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", - "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" - } - }, - "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=" - }, - "os-locale": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", - "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", - "requires": { - "execa": "^0.7.0", - "lcid": "^1.0.0", - "mem": "^1.1.0" - } - }, - "path-parse": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", - "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=" - }, - "resolve": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.5.0.tgz", - "integrity": "sha512-hgoSGrc3pjzAPHNBg+KnFcK2HwlHTs/YrAGUr6qgTVUZmXv1UEXXl0bZNBKMA9fud6lRYFdPGz0xXxycPzmmiw==", - "requires": { - "path-parse": "^1.0.5" - } - }, - "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" - } - }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" - }, - "yargs": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-11.0.0.tgz", - "integrity": "sha512-Rjp+lMYQOWtgqojx1dEWorjCofi1YN7AoFvYV7b1gx/7dAAeuI4kN5SZiEvr0ZmsZTOpDRcCqrpI10L31tFkBw==", - "requires": { - "cliui": "^4.0.0", - "decamelize": "^1.1.1", - "find-up": "^2.1.0", - "get-caller-file": "^1.0.1", - "os-locale": "^2.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", - "yargs-parser": "^9.0.2" - } - }, - "yargs-parser": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-9.0.2.tgz", - "integrity": "sha1-nM9qQ0YP5O1Aqbto9I1DuKaMwHc=", - "requires": { - "camelcase": "^4.1.0" - } - } - } - }, "node-sass-magic-importer": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/node-sass-magic-importer/-/node-sass-magic-importer-5.3.2.tgz", @@ -10815,6 +10563,7 @@ "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" } @@ -11188,7 +10937,8 @@ "p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true }, "p-is-promise": { "version": "2.1.0", @@ -11200,6 +10950,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, "requires": { "p-try": "^1.0.0" } @@ -11208,6 +10959,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, "requires": { "p-limit": "^1.1.0" } @@ -11230,7 +10982,8 @@ "p-try": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=" + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true }, "pako": { "version": "1.0.11", @@ -11354,15 +11107,6 @@ "pinkie-promise": "^2.0.0" } }, - "path-format": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/path-format/-/path-format-1.2.1.tgz", - "integrity": "sha1-qkyffE3odLJP+rxCH7KygG68/VE=", - "requires": { - "is-object": "^1.0.1", - "is-string": "^1.0.4" - } - }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -11377,7 +11121,8 @@ "path-key": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true }, "path-parse": { "version": "1.0.6", @@ -13954,9 +13699,9 @@ } }, "postcss": { - "version": "7.0.26", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.26.tgz", - "integrity": "sha512-IY4oRjpXWYshuTDFxMVkJDtWIk2LhsTlu8bZnbEJA4+bYT16Lvpo8Qv6EvDumhYRgzjZl489pmsY3qVgJQ08nA==", + "version": "7.0.27", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", + "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", "requires": { "chalk": "^2.4.2", "source-map": "^0.6.1", @@ -15627,6 +15372,7 @@ "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" } @@ -15634,7 +15380,8 @@ "shebang-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true }, "shelljs": { "version": "0.6.1", @@ -16376,7 +16123,8 @@ "strip-eof": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true }, "strip-indent": { "version": "1.0.1", diff --git a/package.json b/package.json index 42863298b..bf5f390eb 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,9 @@ "author": "harperdb", "license": "Apache-2.0", "dependencies": { - "@nio/ui-kit": "^2.1.2", + "@nio/ui-kit": "^2.2.2", + "@stripe/react-stripe-js": "^1.0.2", + "@stripe/stripe-js": "^1.0.1", "font-awesome": "^4.7.0", "json-stringify-pretty-compact": "^2.0.0", "react": "^16.12.0", @@ -54,7 +56,7 @@ "eslint-plugin-jsx-a11y": "^6.2.3", "eslint-plugin-react": "^7.18.3", "eslint-plugin-react-hooks": "^2.4.0", - "file-loader": "^5.0.2", + "file-loader": "^5.1.0", "html-loader": "^0.5.5", "html-webpack-inline-source-plugin": "0.0.10", "html-webpack-plugin": "^3.2.0", diff --git a/src/assets/styles/_forms.scss b/src/assets/styles/_forms.scss index 598e73f5d..2c0fae477 100644 --- a/src/assets/styles/_forms.scss +++ b/src/assets/styles/_forms.scss @@ -15,7 +15,6 @@ input:-webkit-autofill:active { border-radius: 4px !important; height: 33px !important; box-shadow: none !important; - text-align: left !important; &::placeholder { text-align: left !important; @@ -23,11 +22,13 @@ input:-webkit-autofill:active { font-family: $font-family-sans; font-weight: $font-weight-light; } -} - - - + &.text-center { + &::placeholder { + text-align: center !important; + } + } +} .nio-check-mark:before { content: "\f00c"; diff --git a/src/assets/styles/_modal.scss b/src/assets/styles/_modal.scss index 2bab4d098..3e5d04ece 100644 --- a/src/assets/styles/_modal.scss +++ b/src/assets/styles/_modal.scss @@ -1,14 +1,13 @@ #new-instance-modal { - .modal-footer { - display: block !important; - } - .new-instance-label { font-weight: 700; - margin-top: 20px; margin-bottom: 5px; } + .instance-form-card { + background: $light-grey-overlay; + } + .radio-button > div { border-bottom: 1px solid $light-grey-overlay; padding: 8px 0; @@ -20,11 +19,15 @@ } .fieldset { - border: 1px solid $light-grey-overlay; - padding: 5px 15px; - border-radius: 4px; + border-top: 1px solid $light-grey-overlay; + padding: 5px 0; max-height: 124px; + margin-bottom: 25px; overflow-x: hidden; overflow-y: auto; + + &:last-child { + margin-bottom: 0; + } } } diff --git a/src/assets/styles/_typography.scss b/src/assets/styles/_typography.scss index 8d1724444..72d9891a2 100755 --- a/src/assets/styles/_typography.scss +++ b/src/assets/styles/_typography.scss @@ -89,7 +89,7 @@ h6 { b, .text-bold, strong { - font-weight: $font-weight-bold !important; + font-weight: $font-weight-bold; } .text-small { diff --git a/src/components/app.js b/src/components/app.js index c471923e5..55ea03a4c 100644 --- a/src/components/app.js +++ b/src/components/app.js @@ -4,19 +4,14 @@ import { Route, Switch, Redirect } from 'react-router-dom'; import Instances from './instances'; import Instance from './instance'; import Login from './login'; -import useLMS from '../stores/lmsData'; -export default () => { - const [lmsData] = useLMS({ auth: false, instances: [] }); - - return ( -
- - - (lmsData && lmsData.instances ? : )} path="/instances" /> - - - -
- ); -} +export default () => ( +
+ + + + + + +
+); diff --git a/src/components/browse/datatable.js b/src/components/browse/datatable.js index 262cbb93f..8a89dfe63 100644 --- a/src/components/browse/datatable.js +++ b/src/components/browse/datatable.js @@ -48,7 +48,7 @@ export default ({ activeTable: { hashAttribute, dataTableColumns }, auth, instan <> - + {schema}  {table && `> ${table} > `} {commaNumbers(tableState.totalRecords)} record{tableState.totalRecords !== 1 ? 's' : ''} diff --git a/src/components/clustering/clusterCard.js b/src/components/clustering/clusterCard.js deleted file mode 100644 index 54f7128d6..000000000 --- a/src/components/clustering/clusterCard.js +++ /dev/null @@ -1,72 +0,0 @@ -import React, { useState } from 'react'; -import { Button, Card, CardBody, Col, Row } from '@nio/ui-kit'; -import useAsyncEffect from 'use-async-effect'; - -import connectToInstance from '../../util/connectToInstance'; -import updateInstanceSubscription from '../../util/updateInstanceSubscription'; - -export default ({ id, host, port, name, is_ssl, connection, clusterPort, schema, table, instanceAuth, refreshInstance, auth, setAuth }) => { - const [buttonState, setButtonState] = useState(false); - const [isConnected, setIsConnected] = useState(false); - const [isLoading, setIsLoading] = useState(false); - - const subscriptions = schema && table && connection && connection.subscriptions ? connection.subscriptions : []; - const channel = schema && table && `${schema}:${table}`; - const channelSubscription = channel && subscriptions.find((s) => s.channel === channel); - const publish = channelSubscription && channelSubscription.publish; - const subscribe = channelSubscription && channelSubscription.subscribe; - - useAsyncEffect(async () => { - if (buttonState && !isLoading) { - setIsLoading(true); - if (buttonState === 'connect') { - await connectToInstance({ id, host, port, clusterPort, auth }); - setIsConnected(true); - } else { - await updateInstanceSubscription({ channel, subscriptions, buttonState, id, host, clusterPort, auth }); - } - setTimeout(() => { - refreshInstance(Date.now()); - setButtonState(false); - setIsLoading(false); - }, 1000); - } - }, [buttonState]); - - return ( - - - - - - {name}
- http{is_ssl && 's'}://{host}:{port} - - {!connection && !isConnected ? ( - - - - ) : ( - - - - - - - - - - - )} -
-
-
- - ); -}; diff --git a/src/components/clustering/datatable.js b/src/components/clustering/datatable.js index e6c2ab971..c684e8240 100644 --- a/src/components/clustering/datatable.js +++ b/src/components/clustering/datatable.js @@ -32,7 +32,7 @@ export default ({ instances, network, schema, table, auth, refreshInstance }) => <> - + {schema} > {table} diff --git a/src/components/clustering/manage.js b/src/components/clustering/manage.js index aeb74c4da..1f4f49803 100644 --- a/src/components/clustering/manage.js +++ b/src/components/clustering/manage.js @@ -6,20 +6,15 @@ import useAsyncEffect from 'use-async-effect'; import EntityManager from '../shared/entityManager'; import useLMS from '../../stores/lmsData'; -import ClusterCard from './clusterCard'; import DataTable from './datatable'; -import useInstanceAuth from '../../stores/instanceAuths'; import setStructureEntities from '../../util/setStructureEntities'; import handleSchemaTableRedirect from '../../util/handleSchemaTableRedirect'; -import defaultTableState from '../../util/defaultTableState'; export default ({ auth, network, refreshInstance, structure }) => { const history = useHistory(); const [lmsData] = useLMS({ auth: false, instances: [] }); const { instance_id, schema, table } = useParams(); - const [instanceAuths, setInstanceAuths] = useInstanceAuth({}); const [entities, setEntities] = useState({ schemas: [], tables: [] }); - const [tableState, setTableState] = useState(defaultTableState); useAsyncEffect(() => { handleSchemaTableRedirect({ entities, instance_id, schema, table, history, targetPath: '/clustering' }); @@ -62,33 +57,6 @@ export default ({ auth, network, refreshInstance, structure }) => { instance_id={instance_id} refreshInstance={refreshInstance} /> - {/* -
- {schema} > {table} -
- - {lmsData.instances.filter((i) => i.id !== instance_id).map((i) => ( - n.name === i.id)} - clusterPort={12345} - schema={schema} - table={table} - refreshInstance={refreshInstance} - auth={auth} - instanceAuth={instanceAuths[i.id]} - setAuth={({ id, user, pass }) => setInstanceAuths({ ...instanceAuths, [id]: { user, pass } })} - /> - ))} - - -
- - {stringify(network.outbound_connections, { maxLength: 20 })} - -
- */}
); diff --git a/src/components/instances.js b/src/components/instances.js index 08f5654c4..6e58d098b 100644 --- a/src/components/instances.js +++ b/src/components/instances.js @@ -1,19 +1,29 @@ import React from 'react'; import { Row } from '@nio/ui-kit'; +import useAsyncEffect from 'use-async-effect'; import TopNav from './topnav'; import useInstanceAuth from '../stores/instanceAuths'; import InstanceCard from './instances/instanceCard'; import NewInstanceCard from './instances/newInstanceCard'; +import useLMS from '../stores/lmsData'; +import defaultLMSData from '../util/defaultLMSData'; +import getInstances from '../util/getInstances'; -export default ({ lmsData }) => { +export default () => { const [instanceAuths, setInstanceAuths] = useInstanceAuth({}); + const [lmsData, setLMSData] = useLMS(defaultLMSData); + + useAsyncEffect(async () => { + const instances = await getInstances({ auth: lmsData.auth }); + setLMSData({ ...lmsData, instances }); + }, []); return ( <> - + {lmsData.instances.map((i) => ( { - const alert = useAlert(); - const [lmsData] = useLMS(defaultLMSData); +export default ({ products, regions, setInstanceDetails, needsCard }) => { const [formData, updateForm] = useState(defaultInstanceFormData); - useAsyncEffect(async () => { + useEffect(() => { if (formData.submitted) { - formData.is_local = instanceType === 'local'; - - console.log(formData); - return false; - - const newInstance = await queryLMS({ - endpoint: 'addInstance', - method: 'POST', - payload: formData, - auth: lmsData.auth, - }); - if (newInstance.error) { - alert.error(newInstance.error); - updateForm({ ...formData, error: newInstance.error, submitted: false }); - } else { - updateForm(defaultInstanceFormData); - } + formData.is_local = false; + formData.is_ssl = true; + setInstanceDetails(formData); } }, [formData]); - return ( -
-
Instance Type
-
- updateForm({ ...formData, stripe_product_id: value })} - options={products} - defaultValue={products[0]} - /> -
+ useEffect(() => { + updateForm({ ...formData, stripe_product_id: products[0].value, instance_region: regions[0].value }); + }, []); -
Instance Region (scroll for more)
-
- updateForm({ ...formData, instance_region: value })} - options={regions} - defaultValue={regions[0]} - /> -
- -
Admin Credentials
-
- - - Username - - + return ( + <> + + +
Instance Name
+
updateForm({ ...formData, user: e.target.value, error: false })} - className="text-center" + onChange={(e) => updateForm({ ...formData, instance_name: e.target.value, error: false })} type="text" - title="username" + title="instance_name" + value={formData.instance_name} /> - - -
- - - Password - - - updateForm({ ...formData, pass: e.target.value, error: false })} - className="text-center" - type="password" - title="password" +
+ +
Admin Credentials
+
+ + + Username + + + updateForm({ ...formData, user: e.target.value, error: false })} + type="text" + title="username" + value={formData.user} + /> + + +
+ + + Password + + + updateForm({ ...formData, pass: e.target.value, error: false })} + type="password" + title="password" + value={formData.pass} + /> + + +
+ +
Instance Type
+
+ updateForm({ ...formData, stripe_product_id: value })} + options={products} + value={formData.stripe_product_id || ''} + defaultValue={products[0]} + /> +
+ +
Instance Region (scroll for more)
+
+ updateForm({ ...formData, instance_region: value })} + options={regions} + value={formData.instance_region || ''} + defaultValue={regions[0]} /> - - -
-
- - - - - - - - -
+
+
+ + + ); }; diff --git a/src/components/instances/confirmOrderForm.js b/src/components/instances/confirmOrderForm.js new file mode 100644 index 000000000..1118fd80b --- /dev/null +++ b/src/components/instances/confirmOrderForm.js @@ -0,0 +1,95 @@ +import React from 'react'; +import { Col, Row, Button, Card, CardBody } from '@nio/ui-kit'; + +export default ({ instancePrice, instanceDetails, instanceSpecs, setConfirmed }) => ( + <> + + + + + Monthly Cost + + + ${instancePrice} + + +
+ + + Instance Name + + + {instanceDetails.instance_name} + + +
+ + + Admin User + + + {instanceDetails.user} + + +
+ + + Admin Password + + + {instanceDetails.pass} + + +
+ + + Instance RAM + + + {instanceSpecs.instance_ram}GB + + +
+ {instanceDetails.is_local ? ( + + + Uses SSL + + + {instanceDetails.is_ssl.toString()} + + + ) : ( + <> + + + Instance Disk Space + + + {instanceSpecs.instance_disk_space_gigs}GB + + +
+ + + Instance Region + + + {instanceDetails.instance_region} + + + + )} +
+
+ + +); diff --git a/src/components/instances/customerPaymentForm.js b/src/components/instances/customerPaymentForm.js new file mode 100644 index 000000000..edbf6c2db --- /dev/null +++ b/src/components/instances/customerPaymentForm.js @@ -0,0 +1,55 @@ +import React, { useState } from 'react'; +import { CardElement, useStripe, useElements } from '@stripe/react-stripe-js'; +import { Button, Card, CardBody } from '@nio/ui-kit'; + +import cardOptions from '../../util/cardOptions'; + +export default ({ setPaymentInfo, instancePrice, instanceInterval }) => { + const stripe = useStripe(); + const elements = useElements(); + const [error, setError] = useState(null); + const [cardComplete, setCardComplete] = useState(false); + const [processing, setProcessing] = useState(false); + const [paymentMethod, setPaymentMethod] = useState(null); + + const handleSubmit = async () => { + if (!stripe || !elements) return; + if (error) { elements.getElement('card').focus(); return; } + if (cardComplete) setProcessing(true); + + const payload = await stripe.createPaymentMethod({ type: 'card', card: elements.getElement(CardElement) }); + setProcessing(false); + + if (payload.error) { + setError(payload.error); + } else { + setPaymentMethod(payload.paymentMethod); + } + }; + + return ( + + + {paymentMethod ? ( + <> +
Card Added Successfully
+
+ Manage payment forms on your account page. +
+ + + ) : ( + <> +
Credit Card Details

+ The instance type you selected has a cost of ${instancePrice}/{instanceInterval}. Please add a credit card to your account using the form below. If you registered using a promo code, your card will not be charged until your promo credits run out. +
+ { setError(e.error); setCardComplete(e.complete); }} /> +
+ + {error &&
{error.message}
} + + )} +
+
+ ); +}; diff --git a/src/components/instances/instanceTypeForm.js b/src/components/instances/instanceTypeForm.js new file mode 100644 index 000000000..242a60753 --- /dev/null +++ b/src/components/instances/instanceTypeForm.js @@ -0,0 +1,43 @@ +import React from 'react'; +import { Row, Col, Card, CardBody, Button } from '@nio/ui-kit'; + +export default ({ setInstanceType }) => ( + + + + +
Add HarperDB Cloud Instance
+
+
    +
  • Free License Tier Available!
  • +
  • Hosted on AWS
  • +
  • Managed by HarperDB
  • +
  • HarperDB License Included
  • +
  • 24/7 Customer Support
  • +
  • Choose RAM and Disk Size
  • +
  • Scale On Demand
  • +
+
+
+ + + + + +
Register A Local Instance
+
+
    +
  • Free License Tier Available!
  • +
  • For HarperDB Instances You Install
  • +
  • Browse Instance Data
  • +
  • Configure Users, Roles, and Clustering
  • +
  • Manage Instance Licenses
  • +
  • Handle Version Upgrades
  • +
  • Instance Credentials Stay Local
  • +
+
+
+ + +
+); diff --git a/src/components/instances/localInstanceForm.js b/src/components/instances/localInstanceForm.js index d46955c49..a6f18369a 100644 --- a/src/components/instances/localInstanceForm.js +++ b/src/components/instances/localInstanceForm.js @@ -1,150 +1,139 @@ import React, { useState } from 'react'; -import { Col, Input, Row, RadioCheckbox, Button } from '@nio/ui-kit'; -import { useAlert } from 'react-alert'; +import { Col, Input, Row, RadioCheckbox, Button, Card, CardBody } from '@nio/ui-kit'; import useAsyncEffect from 'use-async-effect'; import defaultInstanceFormData from '../../util/defaultInstanceFormData'; -import useLMS from '../../stores/lmsData'; -import defaultLMSData from '../../util/defaultLMSData'; -import queryLMS from '../../util/queryLMS'; -export default ({ instanceType, products, setShowForm }) => { - const [lmsData] = useLMS(defaultLMSData); - const alert = useAlert(); +export default ({ products, setInstanceDetails, needsCard }) => { const [formData, updateForm] = useState(defaultInstanceFormData); useAsyncEffect(async () => { if (formData.submitted) { - formData.is_local = instanceType === 'local'; - - console.log(formData); - return false; - - const newInstance = await queryLMS({ - endpoint: 'addInstance', - method: 'POST', - payload: formData, - auth: lmsData.auth, - }); - if (newInstance.error) { - alert.error(newInstance.error); - updateForm({ ...formData, error: newInstance.error, submitted: false }); - } else { - updateForm(defaultInstanceFormData); - } + formData.is_local = true; + setInstanceDetails(formData); } }, [formData]); - return ( -
-
Instance Type
-
- console.log(value)} - options={products} - defaultValue={products[0]} - /> -
+ useAsyncEffect(() => { + if (products) { + updateForm({ ...formData, stripe_product_id: products[0].value }); + } + }, [products]); -
Instance Details
-
- - - Host - - + return ( + <> + + +
Instance Name
+
updateForm({ ...formData, host: e.target.value, error: false })} - className="text-center" + onChange={(e) => updateForm({ ...formData, instance_name: e.target.value, error: false })} type="text" - title="host" - /> - - -
- - - Port - - - updateForm({ ...formData, port: e.target.value, error: false })} - className="text-center" - type="number" - title="port" + title="instance_name" + value={formData.instance_name} /> - - -
- - - SSL - - +
+ + +
Admin Credentials
+
+ + + Username + + + updateForm({ ...formData, user: e.target.value, error: false })} + type="text" + title="username" + value={formData.user} + /> + + +
+ + + Password + + + updateForm({ ...formData, pass: e.target.value, error: false })} + type="password" + title="password" + value={formData.pass} + /> + + +
+ +
Instance Type
+
updateForm({ ...formData, is_ssl: value || false, error: false })} - options={{ label: '', value: true }} + className="radio-button" + type="radio" + onChange={(value) => updateForm({ ...formData, stripe_product_id: value })} + options={products} + value={formData.stripe_product_id || ''} + defaultValue={products[0]} /> - - -
+
-
Admin Credentials
-
- - - Username - - - updateForm({ ...formData, user: e.target.value, error: false })} - className="text-center" - type="text" - title="username" - /> - - -
- - - Password - - - updateForm({ ...formData, pass: e.target.value, error: false })} - className="text-center" - type="password" - title="password" - /> - - -
-
- - - - - - - - -
+
Instance Details
+
+ + + Host + + + updateForm({ ...formData, host: e.target.value, error: false })} + type="text" + title="host" + value={formData.host || ''} + /> + + +
+ + + Port + + + updateForm({ ...formData, port: e.target.value, error: false })} + type="number" + title="port" + value={formData.port || ''} + /> + + +
+ + + SSL + + + updateForm({ ...formData, is_ssl: value || false, error: false })} + options={{ label: '', value: true }} + value={formData.is_ssl} + /> + + +
+ + + + + ); }; diff --git a/src/components/instances/newInstanceCard.js b/src/components/instances/newInstanceCard.js index 8894b3688..160af5814 100644 --- a/src/components/instances/newInstanceCard.js +++ b/src/components/instances/newInstanceCard.js @@ -15,7 +15,7 @@ export default () => { - + {showForm && ()} ); }; diff --git a/src/components/instances/newInstanceModal.js b/src/components/instances/newInstanceModal.js index 52d6ff458..7558a932d 100644 --- a/src/components/instances/newInstanceModal.js +++ b/src/components/instances/newInstanceModal.js @@ -1,59 +1,106 @@ import React, { useState } from 'react'; -import { Button, Modal, ModalHeader, ModalBody, ButtonGroup } from '@nio/ui-kit'; +import { Modal, ModalHeader, ModalBody, Loader } from '@nio/ui-kit'; import useAsyncEffect from 'use-async-effect'; import useLMS from '../../stores/lmsData'; import defaultLMSData from '../../util/defaultLMSData'; -import queryLMS from '../../util/queryLMS'; +import getInstancePrice from '../../util/getInstancePrice'; +import getProductsAndRegions from '../../util/getProductsAndRegions'; +import customerHasChargeableCard from '../../util/customerHasChargeableCard'; +import getCustomer from '../../util/getCustomer'; +import addInstance from '../../util/addInstance'; + import LocalInstanceForm from './localInstanceForm'; import CloudInstanceForm from './cloudInstanceForm'; +import InstanceTypeForm from './instanceTypeForm'; +import CustomerPaymentForm from './customerPaymentForm'; +import ConfirmOrderForm from './confirmOrderForm'; -export default ({ showForm, setShowForm }) => { - const [lmsData] = useLMS(defaultLMSData); - const [instanceType, setInstanceType] = useState('cloud'); +export default ({ setShowForm }) => { + const [lmsData, setLMSData] = useLMS(defaultLMSData); const [products, setProducts] = useState(false); - const [regions, setRegions] = useState(false); + const [purchaseStep, setPurchaseStep] = useState('Select Instance Type'); + const [instanceType, setInstanceType] = useState(false); + const [instancePrice, setInstancePrice] = useState(false); + const [instanceDetails, setInstanceDetails] = useState(false); + const [paymentInfo, setPaymentInfo] = useState(false); + const [confirmed, setConfirmed] = useState(false); + const hasCard = customerHasChargeableCard(lmsData.customer); + const needsCard = instancePrice && !hasCard; + const instanceSpecs = products && products.raw.find((t) => t.stripe_product_id === instanceDetails.stripe_product_id); useAsyncEffect(async () => { - if (showForm) { - const newProducts = await queryLMS({ - endpoint: 'getProducts', - method: 'POST', - auth: lmsData.auth, - }); - setProducts(newProducts); - const newRegions = await queryLMS({ - endpoint: 'getRegions', - method: 'POST', - auth: lmsData.auth, - }); - setRegions(newRegions); + const productsAndRegions = await getProductsAndRegions({ auth: lmsData.auth }); + setProducts(productsAndRegions); + const customer = await getCustomer({ auth: lmsData.auth }); + setLMSData({ ...lmsData, customer }); + }, []); + + useAsyncEffect(() => { + if (instanceType) setPurchaseStep('Instance Details'); + }, [instanceType]); + + useAsyncEffect(() => { + if (instanceDetails) { + const selectedInstancePrice = getInstancePrice({ products: products.raw, instanceDetails }); + setInstancePrice(selectedInstancePrice); + if (selectedInstancePrice && !hasCard) { + // this costs money, so check if they have a payment form + setPurchaseStep('Payment Details'); + } else { + setPurchaseStep('Confirm Instance Details'); + } } - }, [showForm]); + }, [instanceDetails]); + + useAsyncEffect(() => { + if (paymentInfo) setPurchaseStep('Confirm Instance Details'); + }, [paymentInfo]); + + useAsyncEffect(async () => { + if (confirmed) { + await addInstance({ auth: lmsData.auth, payload: { ...instanceDetails, customer_id: lmsData.customer.customer_id } }); + setShowForm(false); + } + }, [confirmed]); return ( - setShowForm(false)}> + setShowForm(false)}> setShowForm(false)}> - Add New Instance + {purchaseStep} - - - - - - {products && regions && instanceType === 'local' ? ( + {!products ? ( + + ) : purchaseStep === 'Select Instance Type' ? ( + + ) : purchaseStep === 'Instance Details' && instanceType === 'local' ? ( ({ label: `${p.instance_ram}GB RAM | ${p.instance_disk_space_gigs}GB Disk Space | $${p.local_price_annual}/year`, value: p.stripe_product_id }))} - setShowForm={setShowForm} + products={products.local} + setInstanceDetails={setInstanceDetails} + needsCard={needsCard} /> - ) : products && regions ? ( + ) : purchaseStep === 'Instance Details' && instanceType === 'cloud' ? ( ({ label: `${p.instance_ram}GB RAM | ${p.instance_disk_space_gigs}GB Disk Space | $${p.cloud_price_monthly}/month`, value: p.stripe_product_id }))} - regions={regions} - setShowForm={setShowForm} + products={products.cloud} + regions={products.regions} + setInstanceDetails={setInstanceDetails} + needsCard={needsCard} + /> + ) : purchaseStep === 'Payment Details' ? ( + + ) : purchaseStep === 'Confirm Instance Details' ? ( + ) : null} diff --git a/src/components/login.js b/src/components/login.js index a77c046d8..f284e50f3 100644 --- a/src/components/login.js +++ b/src/components/login.js @@ -20,16 +20,16 @@ export default () => { if (user !== 'admin' && pass !== 'Abc1234!') { updateForm({ ...formData, error: 'unknown user or password', submitted: false }); } else { - const instances = await queryLMS({ - endpoint: 'getInstances', - method: 'GET', + const currentUser = await queryLMS({ + endpoint: 'getUser', + method: 'POST', auth: { user: formData.user, pass: formData.pass }, }); - if (instances.error) { - updateForm({ ...formData, error: instances.error, submitted: false }); + if (currentUser.error) { + updateForm({ ...formData, error: currentUser.error, submitted: false }); setLMSData(defaultLMSData); } else { - setLMSData({ auth: { user: formData.user, pass: formData.pass }, instances }); + setLMSData({ ...lmsData, auth: { user: formData.user, pass: formData.pass }, currentUser }); history.push('/instances'); } } diff --git a/src/index.js b/src/index.js index e3efe4822..9dfd5e4a0 100644 --- a/src/index.js +++ b/src/index.js @@ -2,21 +2,23 @@ import React from 'react'; import { render } from 'react-dom'; import { BrowserRouter } from 'react-router-dom'; import { positions, Provider } from 'react-alert'; -import { TextDecoder } from 'text-encoding'; +import { Elements } from '@stripe/react-stripe-js'; +import './util/textDecoderPolyfill'; +import stripePromise from './util/stripePromise'; import AlertTemplate from './components/shared/alert'; import App from './components/app'; import './app.scss'; -if (!window.TextDecoder) window.TextDecoder = TextDecoder; - render( ( - - - - - + + + + + + + ), document.getElementById('app'), ); diff --git a/src/util/addInstance.js b/src/util/addInstance.js new file mode 100644 index 000000000..e05af96c1 --- /dev/null +++ b/src/util/addInstance.js @@ -0,0 +1,8 @@ +import queryLMS from './queryLMS'; + +export default async ({ auth, payload }) => queryLMS({ + endpoint: 'addInstance', + method: 'POST', + payload, + auth, +}); diff --git a/src/util/cardOptions.js b/src/util/cardOptions.js new file mode 100644 index 000000000..dda83ff46 --- /dev/null +++ b/src/util/cardOptions.js @@ -0,0 +1,23 @@ +export default { + iconStyle: 'solid', + style: { + base: { + iconColor: '#403b8a', + color: '#212121', + fontWeight: 500, + fontFamily: 'Raleway, Open Sans, Segoe UI, sans-serif', + fontSize: '16px', + fontSmoothing: 'antialiased', + ':-webkit-autofill': { + color: '#fff', + }, + '::placeholder': { + color: '#bcbcbc', + }, + }, + invalid: { + iconColor: '#ea4c89', + color: '#ea4c89', + }, + }, +}; diff --git a/src/util/clusterConfigColumns.js b/src/util/clusterConfigColumns.js index e3d4b2287..9f662b786 100644 --- a/src/util/clusterConfigColumns.js +++ b/src/util/clusterConfigColumns.js @@ -4,6 +4,8 @@ import { Button, ToggleButton } from '@nio/ui-kit'; import connectToInstance from './connectToInstance'; import updateInstanceSubscription from './updateInstanceSubscription'; +const toggleCellPadding = { paddingTop: 3, paddingBottom: 0, paddingLeft: 0, paddingRight: 2 }; + export default ({ auth, refreshInstance }) => [{ Header: 'name', accessor: 'instance_name', @@ -15,22 +17,22 @@ export default ({ auth, refreshInstance }) => [{ }, { Header: 'publish', Cell: ({ - original: { id, host, port, clusterPort, publish, connection, channel, subscriptions } + original: { id, host, port, clusterPort, publish, connection, channel, subscriptions }, }) => ( connection - ? updateInstanceSubscription({ channel, subscriptions, buttonState: 'togglePublish', id, host, clusterPort, auth, refreshInstance })} /> + ? updateInstanceSubscription({ channel, subscriptions, buttonState: 'togglePublish', id, host, clusterPort, auth, refreshInstance })} /> : ), - width: 79, - style: { paddingTop: 2, paddingBottom: 2, paddingLeft: 5, paddingRight: 5 }, + width: 80, + style: toggleCellPadding, }, { Header: 'subscribe', Cell: ({ - original: { id, host, clusterPort, subscribe, connection, channel, subscriptions } + original: { id, host, clusterPort, subscribe, connection, channel, subscriptions }, }) => ( connection - ? updateInstanceSubscription({ channel, subscriptions, buttonState: 'toggleSubscribe', id, host, clusterPort, auth, refreshInstance })} /> + ? updateInstanceSubscription({ channel, subscriptions, buttonState: 'toggleSubscribe', id, host, clusterPort, auth, refreshInstance })} /> : '' ), - width: 79, - style: { paddingTop: 2, paddingBottom: 2, paddingLeft: 5, paddingRight: 5 }, + width: 80, + style: toggleCellPadding, }]; diff --git a/src/util/customerHasChargeableCard.js b/src/util/customerHasChargeableCard.js new file mode 100644 index 000000000..52e3f8fd9 --- /dev/null +++ b/src/util/customerHasChargeableCard.js @@ -0,0 +1 @@ +export default (customer) => customer?.stripe_customer_object?.sources?.data?.[0]?.status === 'chargeable'; diff --git a/src/util/defaultInstanceFormData.js b/src/util/defaultInstanceFormData.js index 869c8c883..8f176de6c 100644 --- a/src/util/defaultInstanceFormData.js +++ b/src/util/defaultInstanceFormData.js @@ -1,11 +1,9 @@ export default { submitted: false, error: false, - user: false, - pass: false, - stripe_product_id: false, - instance_name: false, - instance_region: false, + instance_name: '', + user: '', + pass: '', is_ssl: false, is_local: false, }; diff --git a/src/util/defaultLMSData.js b/src/util/defaultLMSData.js index b93204378..5c6acea12 100644 --- a/src/util/defaultLMSData.js +++ b/src/util/defaultLMSData.js @@ -1 +1 @@ -export default { auth: false, instances: [] }; +export default { auth: false, instances: [], currentUser: false }; diff --git a/src/util/getCustomer.js b/src/util/getCustomer.js new file mode 100644 index 000000000..c9eef6467 --- /dev/null +++ b/src/util/getCustomer.js @@ -0,0 +1,7 @@ +import queryLMS from './queryLMS'; + +export default async ({ auth }) => queryLMS({ + endpoint: 'getCustomer', + method: 'POST', + auth, +}); diff --git a/src/util/getInstancePrice.js b/src/util/getInstancePrice.js new file mode 100644 index 000000000..83689fffb --- /dev/null +++ b/src/util/getInstancePrice.js @@ -0,0 +1,4 @@ +export default ({ products, instanceDetails }) => { + const selectedProduct = products.find((p) => p.stripe_product_id === instanceDetails.stripe_product_id); + return instanceDetails.is_local ? selectedProduct.local_price_annual : selectedProduct.cloud_price_monthly; +}; diff --git a/src/util/getInstances.js b/src/util/getInstances.js new file mode 100644 index 000000000..360c1f3c9 --- /dev/null +++ b/src/util/getInstances.js @@ -0,0 +1,7 @@ +import queryLMS from './queryLMS'; + +export default async ({ auth }) => queryLMS({ + endpoint: 'getInstances', + method: 'POST', + auth, +}); diff --git a/src/util/getProductsAndRegions.js b/src/util/getProductsAndRegions.js new file mode 100644 index 000000000..47c582226 --- /dev/null +++ b/src/util/getProductsAndRegions.js @@ -0,0 +1,20 @@ +import queryLMS from './queryLMS'; + +export default async ({ auth }) => { + const newProducts = await queryLMS({ + endpoint: 'getProducts', + method: 'POST', + auth, + }); + const newRegions = await queryLMS({ + endpoint: 'getRegions', + method: 'POST', + auth, + }); + return { + raw: newProducts, + local: newProducts.map((p) => ({ label: `${p.instance_ram}GB RAM | ${p.instance_disk_space_gigs}GB Disk Space | $${p.local_price_annual}/year`, value: p.stripe_product_id })), + cloud: newProducts.map((p) => ({ label: `${p.instance_ram}GB RAM | ${p.instance_disk_space_gigs}GB Disk Space | $${p.cloud_price_monthly}/month`, value: p.stripe_product_id })), + regions: newRegions, + }; +}; diff --git a/src/util/queryLMS.js b/src/util/queryLMS.js index aa49bb155..0434e4109 100644 --- a/src/util/queryLMS.js +++ b/src/util/queryLMS.js @@ -1,6 +1,8 @@ import instances from '../../mock_data/LMS_API.instances.json'; import products from '../../mock_data/LMS_API.products.json'; import regions from '../../mock_data/LMS_API.aws_regions.json'; +import customer from '../../mock_data/LMS_API.customer.json'; +import user from '../../mock_data/LMS_API.user.json'; // eslint-disable-next-line no-unused-vars export default async ({ endpoint, payload, auth }) => { @@ -19,13 +21,21 @@ export default async ({ endpoint, payload, auth }) => { return request.json(); */ + console.log('Calling API', endpoint); + switch (endpoint) { + case 'getUser': + return user; + case 'getCustomer': + return customer; case 'getInstances': return instances; case 'getProducts': return products; case 'getRegions': return regions; + case 'addInstance': + return { result: true, message: "Instance ", instance_id: 'guid' }; default: return { error: 'unknown endpoint' }; } diff --git a/src/util/stripePromise.js b/src/util/stripePromise.js new file mode 100644 index 000000000..479187c98 --- /dev/null +++ b/src/util/stripePromise.js @@ -0,0 +1,5 @@ +import { loadStripe } from '@stripe/stripe-js'; + +const stripePromise = loadStripe('pk_test_QeKQj2ThQ4mqatyLsXCexOmB00he3FXx2P'); + +export default stripePromise; diff --git a/src/util/textDecoderPolyfill.js b/src/util/textDecoderPolyfill.js new file mode 100644 index 000000000..661da5e89 --- /dev/null +++ b/src/util/textDecoderPolyfill.js @@ -0,0 +1,3 @@ +import { TextDecoder } from 'text-encoding'; + +if (!window.TextDecoder) window.TextDecoder = TextDecoder; diff --git a/webpack.common.js b/webpack.common.js index 6af8ce9d7..1835f5aa3 100644 --- a/webpack.common.js +++ b/webpack.common.js @@ -7,7 +7,6 @@ const TerserPlugin = require('terser-webpack-plugin'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin'); -const HtmlWebpackInlineSourcePlugin = require('html-webpack-inline-source-plugin'); const cssNano = require('cssnano'); const CopyWebpackPlugin = require('copy-webpack-plugin'); /* eslint-enable import/no-extraneous-dependencies */ @@ -17,7 +16,7 @@ module.exports = { output: { path: path.join(__dirname, 'public'), - filename: 'hdb.js', + filename: '[contenthash].js', publicPath: '/', }, @@ -36,8 +35,7 @@ module.exports = { inject: 'body', inlineSource: '.(js|css)$', }), - new HtmlWebpackInlineSourcePlugin(), - new MiniCssExtractPlugin({ filename: 'hdb.css' }), + new MiniCssExtractPlugin({ filename: '[contenthash].css' }), new CopyWebpackPlugin([ { from: path.join(__dirname, '/src/assets/images/'), to: 'images/' }, { from: path.join(__dirname, '/src/assets/fonts/'), to: 'fonts/' }, From e07fe14f793a6ba02d9eec2c087b539995249e8c Mon Sep 17 00:00:00 2001 From: deliciousmonster Date: Mon, 24 Feb 2020 17:22:38 -0700 Subject: [PATCH 12/19] account stuff, billing, style. --- mock_data/LMS_API.customer.json | 28 +++- mock_data/LMS_API.invoices.json | 46 ++++++ src/app.scss | 1 - src/assets/styles/_alert.scss | 8 +- src/assets/styles/_base.scss | 96 +++++++++-- src/assets/styles/_card.scss | 6 +- src/assets/styles/_forms.scss | 29 +++- src/assets/styles/_modal.scss | 15 +- src/assets/styles/_topnav.scss | 76 --------- src/assets/styles/_vars.scss | 6 +- src/components/account/billing.js | 49 ++++++ src/components/account/billing_cardEdit.js | 132 +++++++++++++++ src/components/account/billing_cardStatic.js | 87 ++++++++++ src/components/account/billing_invoices.js | 32 ++++ src/components/account/index.js | 17 ++ src/components/account/profile.js | 156 ++++++++++++++++++ src/components/account/subnav.js | 19 +++ src/components/account/subscription.js | 15 ++ src/components/account/users.js | 15 ++ src/components/app.js | 29 ++-- .../{ => instance}/browse/csvuploader.js | 26 +-- .../{ => instance}/browse/datatable.js | 8 +- src/components/{ => instance}/browse/index.js | 6 +- .../{ => instance}/browse/jsonviewer.js | 4 +- .../{ => instance}/clustering/datatable.js | 4 +- .../{ => instance}/clustering/index.js | 0 .../{ => instance}/clustering/manage.js | 8 +- .../{ => instance}/clustering/setup.js | 4 +- .../{ => instance}/clustering/setup_enable.js | 2 +- .../{ => instance}/clustering/setup_port.js | 2 +- .../{ => instance}/clustering/setup_role.js | 2 +- .../{ => instance}/clustering/setup_user.js | 4 +- .../{instance.js => instance/index.js} | 12 +- .../{users => instance/license}/index.js | 4 +- .../{license => instance/roles}/index.js | 4 +- src/components/instance/subnav.js | 15 ++ src/components/instance/users/index.js | 15 ++ src/components/instances/cardBack.js | 4 +- src/components/instances/cloudInstanceForm.js | 2 +- .../instances/customerPaymentForm.js | 2 +- .../{instances.js => instances/index.js} | 14 +- src/components/instances/instanceTypeForm.js | 14 +- src/components/instances/localInstanceForm.js | 2 +- src/components/instances/newInstanceModal.js | 12 +- src/components/login.js | 17 +- src/components/roles/index.js | 19 --- src/components/shared/alert.js | 19 +-- src/components/shared/entityManagerForm.js | 2 +- src/components/shared/entityManagerRow.js | 2 +- src/components/topnav.js | 68 +++----- src/index.js | 2 +- src/routes/account.js | 11 ++ .../routes.js => routes/instance.js} | 14 +- .../{ => datatable}/clusterConfigColumns.js | 4 +- src/util/{ => datatable}/handleCellValues.js | 0 src/util/{ => datatable}/isImage.js | 0 src/util/{ => format}/commaNumbers.js | 0 .../_queryInstance.js} | 0 .../buildActiveInstanceObject.js | 2 +- src/util/{ => instance}/connectToInstance.js | 2 +- src/util/{ => instance}/createClusterUser.js | 2 +- .../{ => instance}/createClusterUserRole.js | 2 +- src/util/instance/csvDataLoad.js | 3 + .../enableInstanceClustering.js | 2 +- .../getInstanceClusterStatus.js | 2 +- .../getInstanceDataStructure.js | 2 +- src/util/{ => instance}/getInstancePrice.js | 0 src/util/{ => instance}/getTableData.js | 2 +- src/util/instance/getTotalRecords.js | 11 ++ .../{ => instance}/setStructureEntities.js | 0 .../updateInstanceSubscription.js | 2 +- src/util/{queryLMS.js => lms/_queryLMS.js} | 19 ++- src/util/{ => lms}/addInstance.js | 2 +- src/util/lms/addPaymentMethod.js | 8 + src/util/{ => lms}/getCustomer.js | 2 +- src/util/lms/getCustomerInvoices.js | 7 + src/util/{ => lms}/getInstances.js | 2 +- src/util/{ => lms}/getProductsAndRegions.js | 2 +- src/util/lms/getUser.js | 7 + src/util/lms/removePaymentMethod.js | 8 + src/util/{ => state}/defaultAuthFormData.js | 0 .../{ => state}/defaultClusterFormData.js | 0 .../{ => state}/defaultInstanceFormData.js | 0 src/util/{ => state}/defaultLMSData.js | 0 src/util/state/defaultProfileFormData.js | 1 + src/util/{ => state}/defaultTableState.js | 0 src/util/{ => stripe}/cardOptions.js | 2 +- .../{ => stripe}/customerHasChargeableCard.js | 2 +- src/util/{ => stripe}/stripePromise.js | 0 src/util/updateInstanceStates.js | 6 - src/util/useWindowSize.js | 19 --- 91 files changed, 976 insertions(+), 333 deletions(-) create mode 100644 mock_data/LMS_API.invoices.json delete mode 100644 src/assets/styles/_topnav.scss create mode 100644 src/components/account/billing.js create mode 100644 src/components/account/billing_cardEdit.js create mode 100644 src/components/account/billing_cardStatic.js create mode 100644 src/components/account/billing_invoices.js create mode 100644 src/components/account/index.js create mode 100644 src/components/account/profile.js create mode 100644 src/components/account/subnav.js create mode 100644 src/components/account/subscription.js create mode 100644 src/components/account/users.js rename src/components/{ => instance}/browse/csvuploader.js (83%) rename src/components/{ => instance}/browse/datatable.js (94%) rename src/components/{ => instance}/browse/index.js (93%) rename src/components/{ => instance}/browse/jsonviewer.js (93%) rename src/components/{ => instance}/clustering/datatable.js (95%) rename src/components/{ => instance}/clustering/index.js (100%) rename src/components/{ => instance}/clustering/manage.js (87%) rename src/components/{ => instance}/clustering/setup.js (91%) rename src/components/{ => instance}/clustering/setup_enable.js (89%) rename src/components/{ => instance}/clustering/setup_port.js (95%) rename src/components/{ => instance}/clustering/setup_role.js (90%) rename src/components/{ => instance}/clustering/setup_user.js (93%) rename src/components/{instance.js => instance/index.js} (78%) rename src/components/{users => instance/license}/index.js (69%) rename src/components/{license => instance/roles}/index.js (69%) create mode 100644 src/components/instance/subnav.js create mode 100644 src/components/instance/users/index.js rename src/components/{instances.js => instances/index.js} (70%) delete mode 100644 src/components/roles/index.js create mode 100644 src/routes/account.js rename src/{components/routes.js => routes/instance.js} (62%) rename src/util/{ => datatable}/clusterConfigColumns.js (90%) rename src/util/{ => datatable}/handleCellValues.js (100%) rename src/util/{ => datatable}/isImage.js (100%) rename src/util/{ => format}/commaNumbers.js (100%) rename src/util/{queryInstance.js => instance/_queryInstance.js} (100%) rename src/util/{ => instance}/buildActiveInstanceObject.js (92%) rename src/util/{ => instance}/connectToInstance.js (87%) rename src/util/{ => instance}/createClusterUser.js (86%) rename src/util/{ => instance}/createClusterUserRole.js (86%) create mode 100644 src/util/instance/csvDataLoad.js rename src/util/{ => instance}/enableInstanceClustering.js (90%) rename src/util/{ => instance}/getInstanceClusterStatus.js (96%) rename src/util/{ => instance}/getInstanceDataStructure.js (94%) rename src/util/{ => instance}/getInstancePrice.js (100%) rename src/util/{ => instance}/getTableData.js (97%) create mode 100644 src/util/instance/getTotalRecords.js rename src/util/{ => instance}/setStructureEntities.js (100%) rename src/util/{ => instance}/updateInstanceSubscription.js (96%) rename src/util/{queryLMS.js => lms/_queryLMS.js} (51%) rename src/util/{ => lms}/addInstance.js (77%) create mode 100644 src/util/lms/addPaymentMethod.js rename src/util/{ => lms}/getCustomer.js (74%) create mode 100644 src/util/lms/getCustomerInvoices.js rename src/util/{ => lms}/getInstances.js (74%) rename src/util/{ => lms}/getProductsAndRegions.js (94%) create mode 100644 src/util/lms/getUser.js create mode 100644 src/util/lms/removePaymentMethod.js rename src/util/{ => state}/defaultAuthFormData.js (100%) rename src/util/{ => state}/defaultClusterFormData.js (100%) rename src/util/{ => state}/defaultInstanceFormData.js (100%) rename src/util/{ => state}/defaultLMSData.js (100%) create mode 100644 src/util/state/defaultProfileFormData.js rename src/util/{ => state}/defaultTableState.js (100%) rename src/util/{ => stripe}/cardOptions.js (95%) rename src/util/{ => stripe}/customerHasChargeableCard.js (59%) rename src/util/{ => stripe}/stripePromise.js (100%) delete mode 100644 src/util/updateInstanceStates.js delete mode 100644 src/util/useWindowSize.js diff --git a/mock_data/LMS_API.customer.json b/mock_data/LMS_API.customer.json index 95d4c92d8..630f8e261 100644 --- a/mock_data/LMS_API.customer.json +++ b/mock_data/LMS_API.customer.json @@ -8,7 +8,33 @@ "discount": {}, "id": "string", "sources": { - "data": [] + "data": [ + { + "id": "card_1GFpVMDzYFZjJeDRi5I0el9T", + "object": "card", + "address_city": null, + "address_country": null, + "address_line1": null, + "address_line1_check": null, + "address_line2": null, + "address_state": null, + "address_zip": "80209", + "address_zip_check": null, + "brand": "Visa", + "country": "US", + "customer": "cus_GnQM9k8y55BtWs", + "cvc_check": null, + "dynamic_last4": null, + "exp_month": 8, + "exp_year": 2021, + "fingerprint": "ICvbhEovZZAH4OJf", + "funding": "credit", + "last4": "4242", + "metadata": {}, + "name": "Test Name", + "tokenization_method": null + } + ] }, "subscriptions": { "data": [ diff --git a/mock_data/LMS_API.invoices.json b/mock_data/LMS_API.invoices.json new file mode 100644 index 000000000..a797d8f1c --- /dev/null +++ b/mock_data/LMS_API.invoices.json @@ -0,0 +1,46 @@ +[ + { + "id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20d3", + "customer_id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", + "invoice_date": 1582577209446, + "billing_period_start": 1582577109446, + "billing_period_end": 1582577209446, + "total": 3998, + "items": [ + { + "instance_id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20de", + "instance_name": "instance 1", + "stripe_product_id": "product_id", + "invoice_amount": 1999 + }, + { + "instance_id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", + "instance_name": "instance 2", + "stripe_product_id": "product_id", + "invoice_amount": 1999 + } + ] + }, + { + "id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", + "customer_id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", + "invoice_date": 1582577109446, + "billing_period_start": 1582577009446, + "billing_period_end": 1582577109446, + "total": 3998, + "items": [ + { + "instance_id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20de", + "instance_name": "instance 1", + "stripe_product_id": "product_id", + "invoice_amount": 1999 + }, + { + "instance_id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", + "instance_name": "instance 2", + "stripe_product_id": "product_id", + "invoice_amount": 1999 + } + ] + } +] diff --git a/src/app.scss b/src/app.scss index 2e293a928..dee1c3c1a 100644 --- a/src/app.scss +++ b/src/app.scss @@ -17,7 +17,6 @@ @import './assets/styles/code'; // component styles -@import './assets/styles/topnav'; @import './assets/styles/db-browser'; @import './assets/styles/react-table'; @import './assets/styles/login'; diff --git a/src/assets/styles/_alert.scss b/src/assets/styles/_alert.scss index d40e8cdab..0e22bbd19 100644 --- a/src/assets/styles/_alert.scss +++ b/src/assets/styles/_alert.scss @@ -1,15 +1,21 @@ .alert { padding: 13px; border-radius: 6px; - width: 300px; margin-top: 5px; z-index: 9999; + display: flex; + + .icon { + width: 40px; + flex-grow: 0; + } .message { padding-top: 7px; white-space: nowrap; text-overflow: clip; overflow: hidden; + flex-grow: 1; } &.success { diff --git a/src/assets/styles/_base.scss b/src/assets/styles/_base.scss index e8ec16a60..cb6bdd31b 100644 --- a/src/assets/styles/_base.scss +++ b/src/assets/styles/_base.scss @@ -1,5 +1,7 @@ +$minWidth: 314px; + body { - min-width: 320px; + min-width: $minWidth; overflow-x: hidden; #app-bg { @@ -15,27 +17,33 @@ body { } #app { - min-width: 320px; + min-width: $minWidth; #app-container { - min-width: 320px; - margin-top: 70px; - padding: 2rem; - height: calc(100vh - 70px); + min-width: $minWidth; + margin-top: 135px; + padding: 0 2rem; + height: calc(100vh - 135px); @media screen and (max-width: 991px) { - padding: 1rem; + padding: 0 1rem; + } + + &.instances { + margin-top: 100px; + height: calc(100vh - 100px); } } #app-nav { - min-width: 320px; - background: $dark-grey-overlay !important; - padding: .5rem 2rem; + min-width: $minWidth; + background: $black-overlay !important; + padding: 0 2rem; position: fixed; z-index: 99 !important; + height: 60px; @media screen and (max-width: 991px) { - padding: .5rem 1rem; + padding: 0 1rem; } #logo { @@ -46,6 +54,72 @@ body { background-position: top left; background-size: contain; } + .nav-item { + margin-left: 1.5rem; + } + } + + #app-subnav { + width: 100%; + min-width: $minWidth; + background: $dark-grey-overlay !important; + padding: 0 2rem; + position: fixed; + z-index: 98 !important; + height: 40px; + top: 60px !important; + text-align: center; + + .navbar-nav { + width: 100%; + max-width: 400px; + margin: 0 auto; + justify-content: center; + + .nav-item { + padding: 0 10px; + + &:first-child { + padding-left: 0; + } + + &:last-child { + padding-right: 0; + } + } + } + + .nav-link, + a { + padding: 0 !important; + } + + @media screen and (max-width: 991px) { + padding: 0 1rem; + } + } + + .nav-item { + display: inline-block; + white-space: nowrap; + + .nav-link, + a { + display: inline-block; + color: $light-white-overlay !important; + padding: 5px 0 0 !important; + + &.active, + &:hover { + color: $color-white !important; + } + } + + .nav-divider { + display: inline-block; + color: $color-lightgrey !important; + padding: 5px 0 0 !important; + } } } } diff --git a/src/assets/styles/_card.scss b/src/assets/styles/_card.scss index be4db05ca..4c3cc89da 100644 --- a/src/assets/styles/_card.scss +++ b/src/assets/styles/_card.scss @@ -1,12 +1,8 @@ .card { - background: $light-white-overlay !important; + background: $color-white !important; color: $color-black; border-radius: 4px !important; - &.dark { - background: $white-overlay !important; - } - .card-body { padding: 15px; diff --git a/src/assets/styles/_forms.scss b/src/assets/styles/_forms.scss index 2c0fae477..195be0c0e 100644 --- a/src/assets/styles/_forms.scss +++ b/src/assets/styles/_forms.scss @@ -1,24 +1,28 @@ input, select, +textarea, +.stripe-input-holder, input:-webkit-autofill, input:-webkit-autofill:hover, input:-webkit-autofill:focus, input:-webkit-autofill:active { - border: 1px solid $grey-overlay; - background: $white-overlay !important; - color: $color-black !important; + border: 1px solid $color-lightgrey!important; + background: $light-white-overlay !important; + color: $color-black; font-family: $font-family-sans; font-weight: $font-weight-light; font-size: $font-size-root; outline: none !important; - border-color: transparent; border-radius: 4px !important; - height: 33px !important; + height: 36px !important; box-shadow: none !important; + -webkit-text-fill-color: $color-black !important; + -webkit-box-shadow: 0 0 0 1000px $light-white-overlay inset; + transition: background-color 5000s ease-in-out 0s; &::placeholder { text-align: left !important; - color: $color-darkgrey !important; + color: $color-grey !important; font-family: $font-family-sans; font-weight: $font-weight-light; } @@ -30,6 +34,19 @@ input:-webkit-autofill:active { } } +textarea { + height: auto !important; +} + +.form-control { + box-shadow: none !important; +} + +.stripe-input-holder { + padding: 8px 12px 0 12px; + text-align: left; +} + .nio-check-mark:before { content: "\f00c"; } diff --git a/src/assets/styles/_modal.scss b/src/assets/styles/_modal.scss index 3e5d04ece..77ab3f392 100644 --- a/src/assets/styles/_modal.scss +++ b/src/assets/styles/_modal.scss @@ -4,10 +4,19 @@ margin-bottom: 5px; } - .instance-form-card { - background: $light-grey-overlay; + .instance-form-card-holder { + .instance-form-card-body { + background: $light-grey-overlay; + } + + &:first-child{ + @media screen and (max-width: 991px) { + margin-bottom: 1rem; + } + } } + .radio-button > div { border-bottom: 1px solid $light-grey-overlay; padding: 8px 0; @@ -21,7 +30,7 @@ .fieldset { border-top: 1px solid $light-grey-overlay; padding: 5px 0; - max-height: 124px; + max-height: 129px; margin-bottom: 25px; overflow-x: hidden; overflow-y: auto; diff --git a/src/assets/styles/_topnav.scss b/src/assets/styles/_topnav.scss deleted file mode 100644 index 541bfd78a..000000000 --- a/src/assets/styles/_topnav.scss +++ /dev/null @@ -1,76 +0,0 @@ -#app-nav { - - .nav-item { - margin-left: 1.5rem; - display: inline-block; - white-space: nowrap; - - @media screen and (max-width: 991px) { - width: 100%; - } - - .nav-link, - a { - display: inline-block; - color: $color-lightgrey !important; - padding: 5px 0 0 !important; - - &.active, - &:hover { - color: $color-white !important; - } - } - - .nav-divider { - display: inline-block; - color: $color-lightgrey !important; - padding: 5px 0 0 !important; - } - } - - .collapse.show, - .collapsing { - left: 0; - right: 0; - margin: 0; - top: 70px !important; - background: $black-overlay; - text-align: center !important; - border: 0; - padding-top: .5rem; - padding-bottom: .5rem; - - .navbar-nav { - padding: 0; - - .nav-item { - margin-left: 0; - text-align: center; - border-bottom: 1px solid $white-overlay; - - &:last-child { - border-bottom: 0; - } - - .nav-link, - .nav-divider, - a { - display: block; - padding: .5rem 0 !important; - } - - .btn { - margin: 5px auto; - max-width: 250px; - } - - .dropdown-menu { - margin: 0 auto; - max-width: 250px; - } - } - } - } -} - - diff --git a/src/assets/styles/_vars.scss b/src/assets/styles/_vars.scss index 5f09d4803..b22976a2a 100644 --- a/src/assets/styles/_vars.scss +++ b/src/assets/styles/_vars.scss @@ -19,9 +19,9 @@ $white-overlay: rgba(255, 255, 255, .3); $light-white-overlay: rgba(255, 255, 255, .7); $light-grey-overlay: rgba(0, 0, 0, .05); -$grey-overlay: rgba(0, 0, 0, .2); -$dark-grey-overlay: rgba(0, 0, 0, .5); -$black-overlay: rgba(0, 0, 0, .8); +$grey-overlay: rgba(0, 0, 0, .5); +$dark-grey-overlay: rgba(0, 0, 0, .7); +$black-overlay: rgba(0, 0, 0, .9); $fa-font-path: '/fonts'; $border-radius-base: 6px; diff --git a/src/components/account/billing.js b/src/components/account/billing.js new file mode 100644 index 000000000..0474d9a9b --- /dev/null +++ b/src/components/account/billing.js @@ -0,0 +1,49 @@ +import React, { useState } from 'react'; +import { Row, Col, Card, CardBody } from '@nio/ui-kit'; + +import customerHasChargeableCard from '../../util/stripe/customerHasChargeableCard'; +import useLMS from '../../stores/lmsData'; +import defaultLMSData from '../../util/state/defaultLMSData'; +import StaticCard from './billing_cardStatic'; +import EditCard from './billing_cardEdit'; +import Invoices from './billing_invoices'; + +export default () => { + const [lmsData] = useLMS(defaultLMSData); + const [editingCard, setEditingCard] = useState(false); + const customerCard = customerHasChargeableCard(lmsData.customer); + + return ( + + + card + + + {(editingCard || !customerCard) ? ( + + ) : ( + + )} + + + + + invoices + + + + + + + + ); +}; diff --git a/src/components/account/billing_cardEdit.js b/src/components/account/billing_cardEdit.js new file mode 100644 index 000000000..e4bbf3df9 --- /dev/null +++ b/src/components/account/billing_cardEdit.js @@ -0,0 +1,132 @@ +import React, { useState } from 'react'; +import { Button, Row, Col, Loader, Input } from '@nio/ui-kit'; +import { CardNumberElement, CardExpiryElement, CardCvcElement, useElements, useStripe } from '@stripe/react-stripe-js'; +import useAsyncEffect from 'use-async-effect'; + +import cardOptions from '../../util/stripe/cardOptions'; +import useLMS from '../../stores/lmsData'; +import defaultLMSData from '../../util/state/defaultLMSData'; +import addPaymentMethod from '../../util/lms/addPaymentMethod'; +import getCustomer from '../../util/lms/getCustomer'; + +export default ({ setEditingCard, customerCard }) => { + const [lmsData, setLMSData] = useLMS(defaultLMSData); + const [postalCode, setPostalCode] = useState(false); + const [cardSubmitted, setCardSubmitted] = useState(false); + const stripe = useStripe(); + const elements = useElements(); + + const [error, setError] = useState(null); + const [cardComplete, setCardComplete] = useState(false); + const [processing, setProcessing] = useState(false); + + useAsyncEffect(async () => { + if (cardSubmitted && stripe && elements) { + if (cardComplete) setProcessing(true); + + const newCardObject = { + type: 'card', + card: elements.getElement(CardNumberElement), + billing_details: { + address: { + postal_code: postalCode, + }, + }, + }; + + const payload = await stripe.createPaymentMethod(newCardObject); + + console.log(payload); + if (payload.error) { + setError(payload.error); + } else { + await addPaymentMethod({ payment_method_id: payload.paymentMethod.id, stripe_customer_id: lmsData.customer.stripe_customer_object.id, auth: lmsData.auth }); + const customer = await getCustomer({ auth: lmsData.auth }); + setLMSData({ ...lmsData, customer }); + setEditingCard(false); + } + setCardSubmitted(false); + setProcessing(false); + } + }, [cardSubmitted]); + + return ( +
+ + + card number + + +
+ { setError(e.error); setCardComplete(e.complete); }} + /> +
+ +
+
+ + + expiration + + +
+ { setError(e.error); setCardComplete(e.complete); }} + /> +
+ +
+
+ + + cvcc + + +
+ { setError(e.error); setCardComplete(e.complete); }} + /> +
+ +
+
+ + + billing postal code + + + setPostalCode(e.target.value)} + /> + + +
+ {customerCard ? ( + + + + + + + + + ) : ( + + + + + + )} + {error && ( +
+
+ {error.message} +
+ )} +
+ ); +}; diff --git a/src/components/account/billing_cardStatic.js b/src/components/account/billing_cardStatic.js new file mode 100644 index 000000000..5204c197c --- /dev/null +++ b/src/components/account/billing_cardStatic.js @@ -0,0 +1,87 @@ +import React, { useState } from 'react'; +import useAsyncEffect from 'use-async-effect'; +import { Row, Col, Button } from '@nio/ui-kit'; +import { useAlert } from 'react-alert'; + +import removePaymentMethod from '../../util/lms/removePaymentMethod'; + +export default ({ customerId, customerCard, customerStripeId, setEditingCard, auth }) => { + const [removing, setRemoving] = useState(false); + const alert = useAlert(); + + useAsyncEffect(async () => { + if (removing) { + const response = await removePaymentMethod({ + auth, + payload: { + customer_id: customerId, + stripe_card_id: customerCard.id, + customer_stripe_id: customerStripeId, + }, + }); + setRemoving(false); + if (response.result) { + alert.success(response.message); + } else { + alert.error(response.message); + } + } + }, [removing]); + + return ( +
+ + + card number + + +
**** **** **** {customerCard.last4}
+ +
+
+ + + expiration + + +
{customerCard.exp_month} / {customerCard.exp_year}
+ +
+
+ + + cvcc + + +
***
+ +
+
+ + + billing postal code + + +
{customerCard.address_zip}
+ +
+
+ + + + + + + + +
+ ); +}; diff --git a/src/components/account/billing_invoices.js b/src/components/account/billing_invoices.js new file mode 100644 index 000000000..2d176e4a7 --- /dev/null +++ b/src/components/account/billing_invoices.js @@ -0,0 +1,32 @@ +import React, { useState } from 'react'; +import { Row, Col } from '@nio/ui-kit'; +import useAsyncEffect from 'use-async-effect'; + +import getCustomerInvoices from '../../util/lms/getCustomerInvoices'; +import useLMS from '../../stores/lmsData'; +import defaultLMSData from '../../util/state/defaultLMSData'; + +export default () => { + const [lmsData] = useLMS(defaultLMSData); + const [customerInvoices, setCustomerInvoices] = useState([]); + + useAsyncEffect(async () => { + const newCustomerInvoices = await getCustomerInvoices({ auth: lmsData.auth }); + setCustomerInvoices(newCustomerInvoices); + }, []); + + return customerInvoices.map((i) => ( +
+ + + {new Date(i.invoice_date).toLocaleString()} + + + $ + {(i.total / 100).toFixed(2)} + + +
+
+ )); +} diff --git a/src/components/account/index.js b/src/components/account/index.js new file mode 100644 index 000000000..ea2e153d9 --- /dev/null +++ b/src/components/account/index.js @@ -0,0 +1,17 @@ +import React from 'react'; +import { Redirect, Route, Switch } from 'react-router-dom'; + +import routes from '../../routes/account'; +import SubNav from './subnav'; + +export default () => ( + <> + + + {routes.map((route) => ( + + ))} + + + +); diff --git a/src/components/account/profile.js b/src/components/account/profile.js new file mode 100644 index 000000000..f238d8ad3 --- /dev/null +++ b/src/components/account/profile.js @@ -0,0 +1,156 @@ +import React, { useState } from 'react'; +import { Row, Col, Input, Button, Card, CardBody } from '@nio/ui-kit'; +import useAsyncEffect from 'use-async-effect'; + +import defaultProfileFormData from '../../util/state/defaultProfileFormData'; + +export default () => { + const [passwordForm, updatePasswordForm] = useState(defaultProfileFormData); + const [profileForm, updateProfileForm] = useState(defaultProfileFormData); + + useAsyncEffect(async () => { + if (passwordForm.submitted) { + console.log(passwordForm); + // await updatePassword(passwordForm); + } + }, [passwordForm]); + + useAsyncEffect(async () => { + if (profileForm.submitted) { + console.log(profileForm); + // await updateProfile(profileForm); + } + }, [profileForm]); + + return ( + + + profile + + + + + first name + + + updateProfileForm({ ...profileForm, firstname: e.target.value, error: false })} + /> + + +
+ + + last name + + + updateProfileForm({ ...profileForm, lastname: e.target.value, error: false })} + /> + + +
+ + + email address + + + updateProfileForm({ ...profileForm, email: e.target.value, error: false })} + /> + + +
+ {profileForm.error && ( +
+ {profileForm.error} +
+
+ )} + +
+
+ + + password + + + + + current password + + + updatePasswordForm({ ...passwordForm, oldpassword: e.target.value, error: false })} + /> + + +
+ + + new password + + + updatePasswordForm({ ...passwordForm, newpassword: e.target.value, error: false })} + /> + + +
+ + + verify password + + + updatePasswordForm({ ...passwordForm, newpassword2: e.target.value, error: false })} + /> + + +
+ {passwordForm.error && ( +
+ {passwordForm.error} +
+
+ )} + +
+
+ +
+ ); +}; diff --git a/src/components/account/subnav.js b/src/components/account/subnav.js new file mode 100644 index 000000000..c0089d8d1 --- /dev/null +++ b/src/components/account/subnav.js @@ -0,0 +1,19 @@ +import React from 'react'; +import { Navbar, Nav, NavItem } from '@nio/ui-kit'; +import { NavLink } from 'react-router-dom'; + + +export default ({ routes = [] }) => ( + + + +); diff --git a/src/components/account/subscription.js b/src/components/account/subscription.js new file mode 100644 index 000000000..491693356 --- /dev/null +++ b/src/components/account/subscription.js @@ -0,0 +1,15 @@ +import React from 'react'; +import { Card, CardBody, Row, Col } from '@nio/ui-kit'; + +export default () => ( + + + subscription + + + content here + + + + +); diff --git a/src/components/account/users.js b/src/components/account/users.js new file mode 100644 index 000000000..ef1d58070 --- /dev/null +++ b/src/components/account/users.js @@ -0,0 +1,15 @@ +import React from 'react'; +import { Card, CardBody, Row, Col } from '@nio/ui-kit'; + +export default () => ( + + + users + + + content here + + + + +); diff --git a/src/components/app.js b/src/components/app.js index 55ea03a4c..6684b6a9c 100644 --- a/src/components/app.js +++ b/src/components/app.js @@ -1,17 +1,26 @@ import React from 'react'; import { Route, Switch, Redirect } from 'react-router-dom'; +import { useLocation } from 'react-router'; import Instances from './instances'; import Instance from './instance'; import Login from './login'; +import Account from './account'; +import TopNav from './topnav'; -export default () => ( -
- - - - - - -
-); +export default () => { + const location = useLocation(); + + return ( +
+ + + + + + + + +
+ ); +} diff --git a/src/components/browse/csvuploader.js b/src/components/instance/browse/csvuploader.js similarity index 83% rename from src/components/browse/csvuploader.js rename to src/components/instance/browse/csvuploader.js index 16f39dd08..4d0608fd1 100644 --- a/src/components/browse/csvuploader.js +++ b/src/components/instance/browse/csvuploader.js @@ -4,9 +4,10 @@ import { useHistory, useParams } from 'react-router'; import CSVReader from 'react-csv-reader'; import useAsyncEffect from 'use-async-effect'; -import queryInstance from '../../util/queryInstance'; -import commaNumbers from '../../util/commaNumbers'; -import Worker from '../../util/processCSV.worker'; +import getTotalRecords from '../../../util/instance/getTotalRecords'; +import csvDataLoad from '../../../util/instance/csvDataLoad'; +import commaNumbers from '../../../util/format/commaNumbers'; +import Worker from '../../../util/processCSV.worker'; const worker = new Worker(); @@ -20,21 +21,10 @@ export default ({ refreshInstance, instance_id, auth }) => { const [initialRecordCount, setInitialRecordCount] = useState(0); const [fileError, setFileError] = useState(false); - // get the current record count of the table - const getCurrentRecordCount = async () => { - let newTotalRecords = 0; - try { - [{ newTotalRecords }] = await queryInstance({ operation: 'sql', sql: `SELECT count(*) as newTotalRecords FROM ${schema}.${table} ` }, auth); - } catch (e) { - console.log('Failed to get row count'); - } - return newTotalRecords; - }; - // query the table to determine if all the records have been processed. const validateData = async () => { setStatus('validating'); - const validatedCount = await getCurrentRecordCount(); + const validatedCount = await getTotalRecords({ schema, table, auth }); setValidatedRecordCount(validatedCount); if (validatedCount < (newRecordCount + initialRecordCount)) { @@ -54,7 +44,7 @@ export default ({ refreshInstance, instance_id, auth }) => { if (!processedData) return false; setStatus('inserting'); - await queryInstance({ operation: 'csv_data_load', action: 'insert', schema, table, data: processedData }, auth); + await csvDataLoad({ schema, table, data: processedData, auth }); setProcessedData(false); return setTimeout(() => validateData(), 1000); }; @@ -82,11 +72,11 @@ export default ({ refreshInstance, instance_id, auth }) => { history.push(`/instances/${instance_id}/browse/${schema}/${table}`); }; - useAsyncEffect(async () => setInitialRecordCount(await getCurrentRecordCount()), []); + useAsyncEffect(async () => setInitialRecordCount(await getTotalRecords({ schema, table, auth })), []); return ( <> - {schema} > {table} > csv upload + {schema} > {table} > csv upload diff --git a/src/components/browse/datatable.js b/src/components/instance/browse/datatable.js similarity index 94% rename from src/components/browse/datatable.js rename to src/components/instance/browse/datatable.js index 8a89dfe63..57624a927 100644 --- a/src/components/browse/datatable.js +++ b/src/components/instance/browse/datatable.js @@ -5,9 +5,9 @@ import useAsyncEffect from 'use-async-effect'; import useInterval from 'use-interval'; import { Card, CardBody, Col, Row } from '@nio/ui-kit'; -import commaNumbers from '../../util/commaNumbers'; -import getTableData from '../../util/getTableData'; -import defaultTableState from '../../util/defaultTableState'; +import commaNumbers from '../../../util/format/commaNumbers'; +import getTableData from '../../../util/instance/getTableData'; +import defaultTableState from '../../../util/state/defaultTableState'; const dataRefreshInterval = 3000; let tableChangeTimeout = false; @@ -54,7 +54,7 @@ export default ({ activeTable: { hashAttribute, dataTableColumns }, auth, instan {commaNumbers(tableState.totalRecords)} record{tableState.totalRecords !== 1 ? 's' : ''} - + refreshInstance(Date.now())} /> auto setTableState({ ...tableState, autoRefresh: !tableState.autoRefresh, lastUpdate: Date.now() })} /> diff --git a/src/components/browse/index.js b/src/components/instance/browse/index.js similarity index 93% rename from src/components/browse/index.js rename to src/components/instance/browse/index.js index 325139ec3..b31a93a72 100644 --- a/src/components/browse/index.js +++ b/src/components/instance/browse/index.js @@ -5,11 +5,11 @@ import { useParams } from 'react-router-dom'; import useAsyncEffect from 'use-async-effect'; import DataTable from './datatable'; -import EntityManager from '../shared/entityManager'; +import EntityManager from '../../shared/entityManager'; import JSONViewer from './jsonviewer'; import CSVUploader from './csvuploader'; -import setStructureEntities from '../../util/setStructureEntities'; -import handleSchemaTableRedirect from '../../util/handleSchemaTableRedirect'; +import setStructureEntities from '../../../util/instance/setStructureEntities'; +import handleSchemaTableRedirect from '../../../util/handleSchemaTableRedirect'; export default ({ auth, structure, refreshInstance }) => { const history = useHistory(); diff --git a/src/components/browse/jsonviewer.js b/src/components/instance/browse/jsonviewer.js similarity index 93% rename from src/components/browse/jsonviewer.js rename to src/components/instance/browse/jsonviewer.js index 69c94093d..f88065de9 100644 --- a/src/components/browse/jsonviewer.js +++ b/src/components/instance/browse/jsonviewer.js @@ -5,7 +5,7 @@ import { Button, Card, CardBody, Col, Form, Row } from '@nio/ui-kit'; import { useHistory, useParams } from 'react-router'; import useAsyncEffect from 'use-async-effect'; -import queryInstance from '../../util/queryInstance'; +import queryInstance from '../../../util/instance/_queryInstance'; export default ({ newEntityColumns, hashAttribute, instance_id, refreshInstance, auth }) => { const history = useHistory(); @@ -57,7 +57,7 @@ export default ({ newEntityColumns, hashAttribute, instance_id, refreshInstance, return ( <> - {schema} {table && '>'} {table} {action === 'add' ? '> add new' : hash ? `> ${hash}` : ''}  + {schema} {table && '>'} {table} {action === 'add' ? '> add new' : hash ? `> ${hash}` : ''}  diff --git a/src/components/clustering/datatable.js b/src/components/instance/clustering/datatable.js similarity index 95% rename from src/components/clustering/datatable.js rename to src/components/instance/clustering/datatable.js index c684e8240..a34ec8ca5 100644 --- a/src/components/clustering/datatable.js +++ b/src/components/instance/clustering/datatable.js @@ -2,8 +2,8 @@ import React, { useEffect, useState } from 'react'; import ReactTable from 'react-table'; import { Card, CardBody, Col, Row } from '@nio/ui-kit'; -import defaultTableState from '../../util/defaultTableState'; -import clusterConfigColumns from '../../util/clusterConfigColumns'; +import defaultTableState from '../../../util/state/defaultTableState'; +import clusterConfigColumns from '../../../util/datatable/clusterConfigColumns'; export default ({ instances, network, schema, table, auth, refreshInstance }) => { const [tableState, setTableState] = useState(defaultTableState); diff --git a/src/components/clustering/index.js b/src/components/instance/clustering/index.js similarity index 100% rename from src/components/clustering/index.js rename to src/components/instance/clustering/index.js diff --git a/src/components/clustering/manage.js b/src/components/instance/clustering/manage.js similarity index 87% rename from src/components/clustering/manage.js rename to src/components/instance/clustering/manage.js index 1f4f49803..f1cdd7e1e 100644 --- a/src/components/clustering/manage.js +++ b/src/components/instance/clustering/manage.js @@ -4,11 +4,11 @@ import { useParams } from 'react-router-dom'; import { useHistory } from 'react-router'; import useAsyncEffect from 'use-async-effect'; -import EntityManager from '../shared/entityManager'; -import useLMS from '../../stores/lmsData'; +import EntityManager from '../../shared/entityManager'; +import useLMS from '../../../stores/lmsData'; import DataTable from './datatable'; -import setStructureEntities from '../../util/setStructureEntities'; -import handleSchemaTableRedirect from '../../util/handleSchemaTableRedirect'; +import setStructureEntities from '../../../util/instance/setStructureEntities'; +import handleSchemaTableRedirect from '../../../util/handleSchemaTableRedirect'; export default ({ auth, network, refreshInstance, structure }) => { const history = useHistory(); diff --git a/src/components/clustering/setup.js b/src/components/instance/clustering/setup.js similarity index 91% rename from src/components/clustering/setup.js rename to src/components/instance/clustering/setup.js index 7e4362561..38a1111b3 100644 --- a/src/components/clustering/setup.js +++ b/src/components/instance/clustering/setup.js @@ -14,7 +14,7 @@ export default ({ auth, network, refreshInstance }) => { return ( - clustering + clustering HarperDB allows you to replicate data between one or more instances. We call this feature "clustering". @@ -26,7 +26,7 @@ export default ({ auth, network, refreshInstance }) => { - follow the steps below to enable clustering for this instance. + follow the steps below to enable clustering for this instance. { const [submitted, setSubmitted] = useState(false); diff --git a/src/components/clustering/setup_port.js b/src/components/instance/clustering/setup_port.js similarity index 95% rename from src/components/clustering/setup_port.js rename to src/components/instance/clustering/setup_port.js index fc6656306..9f5dbbc75 100644 --- a/src/components/clustering/setup_port.js +++ b/src/components/instance/clustering/setup_port.js @@ -3,7 +3,7 @@ import { Row, Col, Button, Input } from '@nio/ui-kit'; import useAsyncEffect from 'use-async-effect'; import { useAlert } from 'react-alert'; -import defaultFormData from '../../util/defaultClusterFormData'; +import defaultFormData from '../../../util/state/defaultClusterFormData'; export default ({ port, setPort }) => { const alert = useAlert(); diff --git a/src/components/clustering/setup_role.js b/src/components/instance/clustering/setup_role.js similarity index 90% rename from src/components/clustering/setup_role.js rename to src/components/instance/clustering/setup_role.js index a85f08e7c..5e39d9bb8 100644 --- a/src/components/clustering/setup_role.js +++ b/src/components/instance/clustering/setup_role.js @@ -1,7 +1,7 @@ import React from 'react'; import { Row, Col, Button } from '@nio/ui-kit'; -import createClusterUserRole from '../../util/createClusterUserRole'; +import createClusterUserRole from '../../../util/instance/createClusterUserRole'; export default ({ auth, clusterRole, refreshInstance }) => (clusterRole ? ( diff --git a/src/components/clustering/setup_user.js b/src/components/instance/clustering/setup_user.js similarity index 93% rename from src/components/clustering/setup_user.js rename to src/components/instance/clustering/setup_user.js index 123c11092..93283ea9b 100644 --- a/src/components/clustering/setup_user.js +++ b/src/components/instance/clustering/setup_user.js @@ -3,8 +3,8 @@ import { Row, Col, Button, Input } from '@nio/ui-kit'; import useAsyncEffect from 'use-async-effect'; import { useAlert } from 'react-alert'; -import defaultFormData from '../../util/defaultClusterFormData'; -import createClusterUser from '../../util/createClusterUser'; +import defaultFormData from '../../../util/state/defaultClusterFormData'; +import createClusterUser from '../../../util/instance/createClusterUser'; export default ({ clusterUser, clusterRole, auth, refreshInstance }) => { const alert = useAlert(); diff --git a/src/components/instance.js b/src/components/instance/index.js similarity index 78% rename from src/components/instance.js rename to src/components/instance/index.js index b62905f5c..1bb37f259 100644 --- a/src/components/instance.js +++ b/src/components/instance/index.js @@ -2,11 +2,11 @@ import React, { useState } from 'react'; import { Route, Switch, useParams } from 'react-router-dom'; import useAsyncEffect from 'use-async-effect'; -import TopNav from './topnav'; -import routes from './routes'; -import useLMS from '../stores/lmsData'; -import useInstanceAuth from '../stores/instanceAuths'; -import buildActiveInstanceObject from '../util/buildActiveInstanceObject'; +import SubNav from './subnav'; +import routes from '../../routes/instance'; +import useLMS from '../../stores/lmsData'; +import useInstanceAuth from '../../stores/instanceAuths'; +import buildActiveInstanceObject from '../../util/instance/buildActiveInstanceObject'; export default () => { const { instance_id } = useParams(); @@ -26,7 +26,7 @@ export default () => { return ( <> - + {routes.map((route) => { const ThisRouteComponent = route.component; diff --git a/src/components/users/index.js b/src/components/instance/license/index.js similarity index 69% rename from src/components/users/index.js rename to src/components/instance/license/index.js index 227faca60..0bb5b6f23 100644 --- a/src/components/users/index.js +++ b/src/components/instance/license/index.js @@ -4,8 +4,8 @@ import { Card, CardBody, Row, Col } from '@nio/ui-kit'; export default () => ( - Users - + license + content here diff --git a/src/components/license/index.js b/src/components/instance/roles/index.js similarity index 69% rename from src/components/license/index.js rename to src/components/instance/roles/index.js index d5e4428b2..ad8643311 100644 --- a/src/components/license/index.js +++ b/src/components/instance/roles/index.js @@ -4,8 +4,8 @@ import { Card, CardBody, Row, Col } from '@nio/ui-kit'; export default () => ( - License - + roles + content here diff --git a/src/components/instance/subnav.js b/src/components/instance/subnav.js new file mode 100644 index 000000000..580107bf7 --- /dev/null +++ b/src/components/instance/subnav.js @@ -0,0 +1,15 @@ +import React from 'react'; +import { Navbar, Nav, NavItem } from '@nio/ui-kit'; +import { NavLink } from 'react-router-dom'; + +export default ({ routes = [], instanceId }) => ( + + + +); diff --git a/src/components/instance/users/index.js b/src/components/instance/users/index.js new file mode 100644 index 000000000..ef1d58070 --- /dev/null +++ b/src/components/instance/users/index.js @@ -0,0 +1,15 @@ +import React from 'react'; +import { Card, CardBody, Row, Col } from '@nio/ui-kit'; + +export default () => ( + + + users + + + content here + + + + +); diff --git a/src/components/instances/cardBack.js b/src/components/instances/cardBack.js index 343c9b459..df08aaefd 100644 --- a/src/components/instances/cardBack.js +++ b/src/components/instances/cardBack.js @@ -2,8 +2,8 @@ import React, { useState } from 'react'; import { Button, Card, CardBody, Input, Row, Col } from '@nio/ui-kit'; import useAsyncEffect from 'use-async-effect'; import { useAlert } from 'react-alert'; -import queryInstance from '../../util/queryInstance'; -import defaultFormData from '../../util/defaultAuthFormData'; +import queryInstance from '../../util/instance/_queryInstance'; +import defaultFormData from '../../util/state/defaultAuthFormData'; export default ({ id, host, port, is_ssl, setAuth, flipCard, flipState }) => { const alert = useAlert(); diff --git a/src/components/instances/cloudInstanceForm.js b/src/components/instances/cloudInstanceForm.js index d25078b45..0df61924b 100644 --- a/src/components/instances/cloudInstanceForm.js +++ b/src/components/instances/cloudInstanceForm.js @@ -1,7 +1,7 @@ import React, { useState, useEffect } from 'react'; import { Col, Input, Row, RadioCheckbox, Button, Card, CardBody } from '@nio/ui-kit'; -import defaultInstanceFormData from '../../util/defaultInstanceFormData'; +import defaultInstanceFormData from '../../util/state/defaultInstanceFormData'; export default ({ products, regions, setInstanceDetails, needsCard }) => { const [formData, updateForm] = useState(defaultInstanceFormData); diff --git a/src/components/instances/customerPaymentForm.js b/src/components/instances/customerPaymentForm.js index edbf6c2db..63af857c9 100644 --- a/src/components/instances/customerPaymentForm.js +++ b/src/components/instances/customerPaymentForm.js @@ -2,7 +2,7 @@ import React, { useState } from 'react'; import { CardElement, useStripe, useElements } from '@stripe/react-stripe-js'; import { Button, Card, CardBody } from '@nio/ui-kit'; -import cardOptions from '../../util/cardOptions'; +import cardOptions from '../../util/stripe/cardOptions'; export default ({ setPaymentInfo, instancePrice, instanceInterval }) => { const stripe = useStripe(); diff --git a/src/components/instances.js b/src/components/instances/index.js similarity index 70% rename from src/components/instances.js rename to src/components/instances/index.js index 6e58d098b..29db22c49 100644 --- a/src/components/instances.js +++ b/src/components/instances/index.js @@ -2,13 +2,13 @@ import React from 'react'; import { Row } from '@nio/ui-kit'; import useAsyncEffect from 'use-async-effect'; -import TopNav from './topnav'; -import useInstanceAuth from '../stores/instanceAuths'; -import InstanceCard from './instances/instanceCard'; -import NewInstanceCard from './instances/newInstanceCard'; -import useLMS from '../stores/lmsData'; -import defaultLMSData from '../util/defaultLMSData'; -import getInstances from '../util/getInstances'; +import TopNav from '../topnav'; +import useInstanceAuth from '../../stores/instanceAuths'; +import InstanceCard from './instanceCard'; +import NewInstanceCard from './newInstanceCard'; +import useLMS from '../../stores/lmsData'; +import defaultLMSData from '../../util/state/defaultLMSData'; +import getInstances from '../../util/lms/getInstances'; export default () => { const [instanceAuths, setInstanceAuths] = useInstanceAuth({}); diff --git a/src/components/instances/instanceTypeForm.js b/src/components/instances/instanceTypeForm.js index 242a60753..fc360eca6 100644 --- a/src/components/instances/instanceTypeForm.js +++ b/src/components/instances/instanceTypeForm.js @@ -3,9 +3,9 @@ import { Row, Col, Card, CardBody, Button } from '@nio/ui-kit'; export default ({ setInstanceType }) => ( - + - +
Add HarperDB Cloud Instance

    @@ -17,13 +17,14 @@ export default ({ setInstanceType }) => (
  • Choose RAM and Disk Size
  • Scale On Demand
+
+
- - + - +
Register A Local Instance

    @@ -35,9 +36,10 @@ export default ({ setInstanceType }) => (
  • Handle Version Upgrades
  • Instance Credentials Stay Local
+
+
-
); diff --git a/src/components/instances/localInstanceForm.js b/src/components/instances/localInstanceForm.js index a6f18369a..cdf287ccb 100644 --- a/src/components/instances/localInstanceForm.js +++ b/src/components/instances/localInstanceForm.js @@ -2,7 +2,7 @@ import React, { useState } from 'react'; import { Col, Input, Row, RadioCheckbox, Button, Card, CardBody } from '@nio/ui-kit'; import useAsyncEffect from 'use-async-effect'; -import defaultInstanceFormData from '../../util/defaultInstanceFormData'; +import defaultInstanceFormData from '../../util/state/defaultInstanceFormData'; export default ({ products, setInstanceDetails, needsCard }) => { const [formData, updateForm] = useState(defaultInstanceFormData); diff --git a/src/components/instances/newInstanceModal.js b/src/components/instances/newInstanceModal.js index 7558a932d..ec1f8c56b 100644 --- a/src/components/instances/newInstanceModal.js +++ b/src/components/instances/newInstanceModal.js @@ -3,12 +3,12 @@ import { Modal, ModalHeader, ModalBody, Loader } from '@nio/ui-kit'; import useAsyncEffect from 'use-async-effect'; import useLMS from '../../stores/lmsData'; -import defaultLMSData from '../../util/defaultLMSData'; -import getInstancePrice from '../../util/getInstancePrice'; -import getProductsAndRegions from '../../util/getProductsAndRegions'; -import customerHasChargeableCard from '../../util/customerHasChargeableCard'; -import getCustomer from '../../util/getCustomer'; -import addInstance from '../../util/addInstance'; +import defaultLMSData from '../../util/state/defaultLMSData'; +import getInstancePrice from '../../util/instance/getInstancePrice'; +import getProductsAndRegions from '../../util/lms/getProductsAndRegions'; +import customerHasChargeableCard from '../../util/stripe/customerHasChargeableCard'; +import getCustomer from '../../util/lms/getCustomer'; +import addInstance from '../../util/lms/addInstance'; import LocalInstanceForm from './localInstanceForm'; import CloudInstanceForm from './cloudInstanceForm'; diff --git a/src/components/login.js b/src/components/login.js index f284e50f3..bcb7e4350 100644 --- a/src/components/login.js +++ b/src/components/login.js @@ -4,9 +4,10 @@ import useAsyncEffect from 'use-async-effect'; import { useHistory } from 'react-router'; import useLMS from '../stores/lmsData'; -import queryLMS from '../util/queryLMS'; -import defaultFormData from '../util/defaultAuthFormData'; -import defaultLMSData from '../util/defaultLMSData'; +import getUser from '../util/lms/getUser'; +import getCustomer from '../util/lms/getCustomer'; +import defaultFormData from '../util/state/defaultAuthFormData'; +import defaultLMSData from '../util/state/defaultLMSData'; export default () => { const [lmsData, setLMSData] = useLMS(defaultLMSData); @@ -20,16 +21,14 @@ export default () => { if (user !== 'admin' && pass !== 'Abc1234!') { updateForm({ ...formData, error: 'unknown user or password', submitted: false }); } else { - const currentUser = await queryLMS({ - endpoint: 'getUser', - method: 'POST', - auth: { user: formData.user, pass: formData.pass }, - }); + const currentUser = await getUser({ auth: { user: formData.user, pass: formData.pass } }); + if (currentUser.error) { updateForm({ ...formData, error: currentUser.error, submitted: false }); setLMSData(defaultLMSData); } else { - setLMSData({ ...lmsData, auth: { user: formData.user, pass: formData.pass }, currentUser }); + const customer = await getCustomer({ auth: { user: formData.user, pass: formData.pass } }); + setLMSData({ ...lmsData, auth: { user: formData.user, pass: formData.pass }, currentUser, customer }); history.push('/instances'); } } diff --git a/src/components/roles/index.js b/src/components/roles/index.js deleted file mode 100644 index 20f2a2513..000000000 --- a/src/components/roles/index.js +++ /dev/null @@ -1,19 +0,0 @@ -import React from 'react'; -import { Code } from '@nio/ui-kit'; - -export default ({ auth, structure, network, refreshInstance }) => (!network ? ( - -) : network.is_enabled && network.cluster_user && network.cluster_role ? ( - -) : ( - -)); diff --git a/src/components/shared/alert.js b/src/components/shared/alert.js index 7c405de03..529da2429 100644 --- a/src/components/shared/alert.js +++ b/src/components/shared/alert.js @@ -1,17 +1,14 @@ import React from 'react'; -import { Row, Col } from '@nio/ui-kit'; export default ({ message, options, close }) => (
- - - {options.type === 'info' && } - {options.type === 'success' && } - {options.type === 'error' && } - - - {message} - - +
+ {options.type === 'info' && } + {options.type === 'success' && } + {options.type === 'error' && } +
+
+ {message} +
); diff --git a/src/components/shared/entityManagerForm.js b/src/components/shared/entityManagerForm.js index f7c35f53b..553c3eeaa 100644 --- a/src/components/shared/entityManagerForm.js +++ b/src/components/shared/entityManagerForm.js @@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react'; import { Row, Col, Button, Input, Form } from '@nio/ui-kit'; import { useHistory } from 'react-router'; -import queryInstance from '../../util/queryInstance'; +import queryInstance from '../../util/instance/_queryInstance'; export default ({ items, itemType, activeSchema, toggleDropItem, toggleCreate, baseUrl, auth, refreshInstance }) => { const history = useHistory(); diff --git a/src/components/shared/entityManagerRow.js b/src/components/shared/entityManagerRow.js index b9f384d99..dc2996157 100644 --- a/src/components/shared/entityManagerRow.js +++ b/src/components/shared/entityManagerRow.js @@ -2,7 +2,7 @@ import React, { useState } from 'react'; import { Row, Col, Button } from '@nio/ui-kit'; import { useHistory, useParams } from 'react-router'; -import queryInstance from '../../util/queryInstance'; +import queryInstance from '../../util/instance/_queryInstance'; export default ({ item, itemType, baseUrl, isActive, toggleDropItem, isDropping, activeSchema, auth, refreshInstance }) => { const history = useHistory(); diff --git a/src/components/topnav.js b/src/components/topnav.js index 4fcf57848..74263d5a9 100644 --- a/src/components/topnav.js +++ b/src/components/topnav.js @@ -1,57 +1,37 @@ -import React, { useState } from 'react'; -import { Navbar, NavbarToggler, Nav, NavItem, Collapse, NavLink as DumbLink } from '@nio/ui-kit'; -import { useParams, useHistory } from 'react-router'; +import React from 'react'; +import { Navbar, Nav, NavItem, NavLink as DumbLink } from '@nio/ui-kit'; +import { useHistory } from 'react-router'; import { NavLink } from 'react-router-dom'; import useLMS from '../stores/lmsData'; -import defaultLMSData from '../util/defaultLMSData'; +import defaultLMSData from '../util/state/defaultLMSData'; -export default ({ routes = false }) => { - const { instance_id } = useParams(); +export default () => { const history = useHistory(); const [lmsData, setLMSData] = useLMS(defaultLMSData); - const [navOpen, toggleNav] = useState(false); - const thisInstance = lmsData && lmsData.instances.find((i) => i.id === instance_id); - return ( - + return lmsData.auth && ( +
- toggleNav(!navOpen)} isOpen={navOpen} /> - - - + ); }; diff --git a/src/index.js b/src/index.js index 9dfd5e4a0..a1503d242 100644 --- a/src/index.js +++ b/src/index.js @@ -5,7 +5,7 @@ import { positions, Provider } from 'react-alert'; import { Elements } from '@stripe/react-stripe-js'; import './util/textDecoderPolyfill'; -import stripePromise from './util/stripePromise'; +import stripePromise from './util/stripe/stripePromise'; import AlertTemplate from './components/shared/alert'; import App from './components/app'; import './app.scss'; diff --git a/src/routes/account.js b/src/routes/account.js new file mode 100644 index 000000000..663d1ace0 --- /dev/null +++ b/src/routes/account.js @@ -0,0 +1,11 @@ +import Profile from '../components/account/profile'; +import Users from '../components/account/users'; +import Subscription from '../components/account/subscription'; +import Billing from '../components/account/billing'; + +export default [ + { component: Profile, path: '/account/profile', link: 'profile', icon: 'user' }, + { component: Users, path: '/account/users', link: 'users', icon: 'users' }, + { component: Subscription, path: '/account/subscription', link: 'subscription', icon: 'list' }, + { component: Billing, path: '/account/billing', link: 'billing', icon: 'credit-card-alt' }, +]; diff --git a/src/components/routes.js b/src/routes/instance.js similarity index 62% rename from src/components/routes.js rename to src/routes/instance.js index 73b42dbce..f880615ac 100644 --- a/src/components/routes.js +++ b/src/routes/instance.js @@ -1,15 +1,13 @@ -import Browse from './browse'; -import Clustering from './clustering'; -import License from './license'; -import Users from './users'; -import Roles from './roles'; +import Browse from '../components/instance/browse'; +import Clustering from '../components/instance/clustering'; +import License from '../components/instance/license'; +import Users from '../components/instance/users'; +import Roles from '../components/instance/roles'; -const routes = [ +export default [ { component: Browse, path: '/instances/:instance_id/browse/:schema?/:table?/:action?/:hash?', link: 'browse' }, { component: Clustering, path: '/instances/:instance_id/clustering/:schema?/:table?', link: 'clustering' }, { component: License, path: '/instances/:instance_id/license', link: 'license' }, { component: Users, path: '/instances/:instance_id/users', link: 'users' }, { component: Roles, path: '/instances/:instance_id/roles', link: 'roles' }, ]; - -export default routes; diff --git a/src/util/clusterConfigColumns.js b/src/util/datatable/clusterConfigColumns.js similarity index 90% rename from src/util/clusterConfigColumns.js rename to src/util/datatable/clusterConfigColumns.js index 9f662b786..d044e8a69 100644 --- a/src/util/clusterConfigColumns.js +++ b/src/util/datatable/clusterConfigColumns.js @@ -1,8 +1,8 @@ import React from 'react'; import { Button, ToggleButton } from '@nio/ui-kit'; -import connectToInstance from './connectToInstance'; -import updateInstanceSubscription from './updateInstanceSubscription'; +import connectToInstance from '../instance/connectToInstance'; +import updateInstanceSubscription from '../instance/updateInstanceSubscription'; const toggleCellPadding = { paddingTop: 3, paddingBottom: 0, paddingLeft: 0, paddingRight: 2 }; diff --git a/src/util/handleCellValues.js b/src/util/datatable/handleCellValues.js similarity index 100% rename from src/util/handleCellValues.js rename to src/util/datatable/handleCellValues.js diff --git a/src/util/isImage.js b/src/util/datatable/isImage.js similarity index 100% rename from src/util/isImage.js rename to src/util/datatable/isImage.js diff --git a/src/util/commaNumbers.js b/src/util/format/commaNumbers.js similarity index 100% rename from src/util/commaNumbers.js rename to src/util/format/commaNumbers.js diff --git a/src/util/queryInstance.js b/src/util/instance/_queryInstance.js similarity index 100% rename from src/util/queryInstance.js rename to src/util/instance/_queryInstance.js diff --git a/src/util/buildActiveInstanceObject.js b/src/util/instance/buildActiveInstanceObject.js similarity index 92% rename from src/util/buildActiveInstanceObject.js rename to src/util/instance/buildActiveInstanceObject.js index 6f2b1c1ce..2c6b47013 100644 --- a/src/util/buildActiveInstanceObject.js +++ b/src/util/instance/buildActiveInstanceObject.js @@ -1,4 +1,4 @@ -import queryInstance from './queryInstance'; +import queryInstance from './_queryInstance'; import getInstanceDataStructure from './getInstanceDataStructure'; import getInstanceClusterStatus from './getInstanceClusterStatus'; diff --git a/src/util/connectToInstance.js b/src/util/instance/connectToInstance.js similarity index 87% rename from src/util/connectToInstance.js rename to src/util/instance/connectToInstance.js index 1ff6de3e7..3957a05ce 100644 --- a/src/util/connectToInstance.js +++ b/src/util/instance/connectToInstance.js @@ -1,4 +1,4 @@ -import queryInstance from './queryInstance'; +import queryInstance from './_queryInstance'; export default async ({ id, host, clusterPort, auth, refreshInstance }) => { await queryInstance({ operation: 'remove_node', name: id }, auth); diff --git a/src/util/createClusterUser.js b/src/util/instance/createClusterUser.js similarity index 86% rename from src/util/createClusterUser.js rename to src/util/instance/createClusterUser.js index c36a2d25c..d221e7e41 100644 --- a/src/util/createClusterUser.js +++ b/src/util/instance/createClusterUser.js @@ -1,4 +1,4 @@ -import queryInstance from './queryInstance'; +import queryInstance from './_queryInstance'; export default async ({ username, password, role, auth, refreshInstance }) => { const query = { diff --git a/src/util/createClusterUserRole.js b/src/util/instance/createClusterUserRole.js similarity index 86% rename from src/util/createClusterUserRole.js rename to src/util/instance/createClusterUserRole.js index 9920d3cb9..568bbbca3 100644 --- a/src/util/createClusterUserRole.js +++ b/src/util/instance/createClusterUserRole.js @@ -1,4 +1,4 @@ -import queryInstance from './queryInstance'; +import queryInstance from './_queryInstance'; export default async ({ auth, refreshInstance }) => { const query = { diff --git a/src/util/instance/csvDataLoad.js b/src/util/instance/csvDataLoad.js new file mode 100644 index 000000000..e00b8eab1 --- /dev/null +++ b/src/util/instance/csvDataLoad.js @@ -0,0 +1,3 @@ +import queryInstance from './_queryInstance'; + +export default async ({ schema, table, data, auth }) => queryInstance({ operation: 'csv_data_load', action: 'insert', schema, table, data }, auth); diff --git a/src/util/enableInstanceClustering.js b/src/util/instance/enableInstanceClustering.js similarity index 90% rename from src/util/enableInstanceClustering.js rename to src/util/instance/enableInstanceClustering.js index 2d9ceff2e..52420e230 100644 --- a/src/util/enableInstanceClustering.js +++ b/src/util/instance/enableInstanceClustering.js @@ -1,4 +1,4 @@ -import queryInstance from './queryInstance'; +import queryInstance from './_queryInstance'; export default async ({ instanceId, username, port, auth, refreshInstance }) => { const query = { diff --git a/src/util/getInstanceClusterStatus.js b/src/util/instance/getInstanceClusterStatus.js similarity index 96% rename from src/util/getInstanceClusterStatus.js rename to src/util/instance/getInstanceClusterStatus.js index c365a02a2..869065fce 100644 --- a/src/util/getInstanceClusterStatus.js +++ b/src/util/instance/getInstanceClusterStatus.js @@ -1,4 +1,4 @@ -import queryInstance from './queryInstance'; +import queryInstance from './_queryInstance'; const processConnections = (connections) => (connections ? connections.filter((c) => c.host_address.indexOf('::ffff') === -1).map((c) => ({ name: c.node_name, diff --git a/src/util/getInstanceDataStructure.js b/src/util/instance/getInstanceDataStructure.js similarity index 94% rename from src/util/getInstanceDataStructure.js rename to src/util/instance/getInstanceDataStructure.js index f935b14d2..81d0399b3 100644 --- a/src/util/getInstanceDataStructure.js +++ b/src/util/instance/getInstanceDataStructure.js @@ -1,4 +1,4 @@ -import handleCellValues from './handleCellValues'; +import handleCellValues from '../datatable/handleCellValues'; export default (dbResponse) => { const dbStructure = {}; diff --git a/src/util/getInstancePrice.js b/src/util/instance/getInstancePrice.js similarity index 100% rename from src/util/getInstancePrice.js rename to src/util/instance/getInstancePrice.js diff --git a/src/util/getTableData.js b/src/util/instance/getTableData.js similarity index 97% rename from src/util/getTableData.js rename to src/util/instance/getTableData.js index 309f776ff..4b127375e 100644 --- a/src/util/getTableData.js +++ b/src/util/instance/getTableData.js @@ -1,4 +1,4 @@ -import queryInstance from './queryInstance'; +import queryInstance from './_queryInstance'; export default async ({ schema, table, tableState, auth }) => { if (!tableState.sorted.length) return false; diff --git a/src/util/instance/getTotalRecords.js b/src/util/instance/getTotalRecords.js new file mode 100644 index 000000000..2afaf3aa9 --- /dev/null +++ b/src/util/instance/getTotalRecords.js @@ -0,0 +1,11 @@ +import queryInstance from './_queryInstance'; + +export default async ({ schema, table, auth }) => { + let newTotalRecords = 0; + try { + [{ newTotalRecords }] = await queryInstance({ operation: 'sql', sql: `SELECT count(*) as newTotalRecords FROM ${schema}.${table} ` }, auth); + } catch (e) { + console.log('Failed to get row count'); + } + return newTotalRecords; +}; diff --git a/src/util/setStructureEntities.js b/src/util/instance/setStructureEntities.js similarity index 100% rename from src/util/setStructureEntities.js rename to src/util/instance/setStructureEntities.js diff --git a/src/util/updateInstanceSubscription.js b/src/util/instance/updateInstanceSubscription.js similarity index 96% rename from src/util/updateInstanceSubscription.js rename to src/util/instance/updateInstanceSubscription.js index 21dd19c21..759e1a04a 100644 --- a/src/util/updateInstanceSubscription.js +++ b/src/util/instance/updateInstanceSubscription.js @@ -1,4 +1,4 @@ -import queryInstance from './queryInstance'; +import queryInstance from './_queryInstance'; export default async ({ channel, subscriptions, buttonState, id, host, clusterPort, auth, refreshInstance }) => { const existingChannelSubscriptionIndex = subscriptions.findIndex((s) => s.channel === channel); diff --git a/src/util/queryLMS.js b/src/util/lms/_queryLMS.js similarity index 51% rename from src/util/queryLMS.js rename to src/util/lms/_queryLMS.js index 0434e4109..d0e136343 100644 --- a/src/util/queryLMS.js +++ b/src/util/lms/_queryLMS.js @@ -1,8 +1,9 @@ -import instances from '../../mock_data/LMS_API.instances.json'; -import products from '../../mock_data/LMS_API.products.json'; -import regions from '../../mock_data/LMS_API.aws_regions.json'; -import customer from '../../mock_data/LMS_API.customer.json'; -import user from '../../mock_data/LMS_API.user.json'; +import instances from '../../../mock_data/LMS_API.instances.json'; +import products from '../../../mock_data/LMS_API.products.json'; +import regions from '../../../mock_data/LMS_API.aws_regions.json'; +import customer from '../../../mock_data/LMS_API.customer.json'; +import user from '../../../mock_data/LMS_API.user.json'; +import invoices from '../../../mock_data/LMS_API.invoices.json'; // eslint-disable-next-line no-unused-vars export default async ({ endpoint, payload, auth }) => { @@ -34,8 +35,14 @@ export default async ({ endpoint, payload, auth }) => { return products; case 'getRegions': return regions; + case 'getCustomerInvoices': + return invoices; + case 'addPaymentMethod': + return { result: true, message: 'Payment method added to account' }; + case 'removePaymentMethod': + return { result: true, message: 'Payment method removed from account.' }; case 'addInstance': - return { result: true, message: "Instance ", instance_id: 'guid' }; + return { result: true, message: 'Instance ', instance_id: 'guid' }; default: return { error: 'unknown endpoint' }; } diff --git a/src/util/addInstance.js b/src/util/lms/addInstance.js similarity index 77% rename from src/util/addInstance.js rename to src/util/lms/addInstance.js index e05af96c1..dbd5a376f 100644 --- a/src/util/addInstance.js +++ b/src/util/lms/addInstance.js @@ -1,4 +1,4 @@ -import queryLMS from './queryLMS'; +import queryLMS from './_queryLMS'; export default async ({ auth, payload }) => queryLMS({ endpoint: 'addInstance', diff --git a/src/util/lms/addPaymentMethod.js b/src/util/lms/addPaymentMethod.js new file mode 100644 index 000000000..4ed8be754 --- /dev/null +++ b/src/util/lms/addPaymentMethod.js @@ -0,0 +1,8 @@ +import queryLMS from './_queryLMS'; + +export default async ({ auth, payload }) => queryLMS({ + endpoint: 'addPaymentMethod', + method: 'POST', + payload, + auth, +}); diff --git a/src/util/getCustomer.js b/src/util/lms/getCustomer.js similarity index 74% rename from src/util/getCustomer.js rename to src/util/lms/getCustomer.js index c9eef6467..cf042514e 100644 --- a/src/util/getCustomer.js +++ b/src/util/lms/getCustomer.js @@ -1,4 +1,4 @@ -import queryLMS from './queryLMS'; +import queryLMS from './_queryLMS'; export default async ({ auth }) => queryLMS({ endpoint: 'getCustomer', diff --git a/src/util/lms/getCustomerInvoices.js b/src/util/lms/getCustomerInvoices.js new file mode 100644 index 000000000..988f1d536 --- /dev/null +++ b/src/util/lms/getCustomerInvoices.js @@ -0,0 +1,7 @@ +import queryLMS from './_queryLMS'; + +export default async ({ auth }) => queryLMS({ + endpoint: 'getCustomerInvoices', + method: 'POST', + auth, +}); diff --git a/src/util/getInstances.js b/src/util/lms/getInstances.js similarity index 74% rename from src/util/getInstances.js rename to src/util/lms/getInstances.js index 360c1f3c9..6748f3d7c 100644 --- a/src/util/getInstances.js +++ b/src/util/lms/getInstances.js @@ -1,4 +1,4 @@ -import queryLMS from './queryLMS'; +import queryLMS from './_queryLMS'; export default async ({ auth }) => queryLMS({ endpoint: 'getInstances', diff --git a/src/util/getProductsAndRegions.js b/src/util/lms/getProductsAndRegions.js similarity index 94% rename from src/util/getProductsAndRegions.js rename to src/util/lms/getProductsAndRegions.js index 47c582226..f6251e283 100644 --- a/src/util/getProductsAndRegions.js +++ b/src/util/lms/getProductsAndRegions.js @@ -1,4 +1,4 @@ -import queryLMS from './queryLMS'; +import queryLMS from './_queryLMS'; export default async ({ auth }) => { const newProducts = await queryLMS({ diff --git a/src/util/lms/getUser.js b/src/util/lms/getUser.js new file mode 100644 index 000000000..a860cf02a --- /dev/null +++ b/src/util/lms/getUser.js @@ -0,0 +1,7 @@ +import queryLMS from './_queryLMS'; + +export default async ({ auth }) => queryLMS({ + endpoint: 'getUser', + method: 'POST', + auth, +}); diff --git a/src/util/lms/removePaymentMethod.js b/src/util/lms/removePaymentMethod.js new file mode 100644 index 000000000..6b8a96f45 --- /dev/null +++ b/src/util/lms/removePaymentMethod.js @@ -0,0 +1,8 @@ +import queryLMS from './_queryLMS'; + +export default async ({ auth, payload }) => queryLMS({ + endpoint: 'removePaymentMethod', + method: 'POST', + payload, + auth, +}); diff --git a/src/util/defaultAuthFormData.js b/src/util/state/defaultAuthFormData.js similarity index 100% rename from src/util/defaultAuthFormData.js rename to src/util/state/defaultAuthFormData.js diff --git a/src/util/defaultClusterFormData.js b/src/util/state/defaultClusterFormData.js similarity index 100% rename from src/util/defaultClusterFormData.js rename to src/util/state/defaultClusterFormData.js diff --git a/src/util/defaultInstanceFormData.js b/src/util/state/defaultInstanceFormData.js similarity index 100% rename from src/util/defaultInstanceFormData.js rename to src/util/state/defaultInstanceFormData.js diff --git a/src/util/defaultLMSData.js b/src/util/state/defaultLMSData.js similarity index 100% rename from src/util/defaultLMSData.js rename to src/util/state/defaultLMSData.js diff --git a/src/util/state/defaultProfileFormData.js b/src/util/state/defaultProfileFormData.js new file mode 100644 index 000000000..77ef68558 --- /dev/null +++ b/src/util/state/defaultProfileFormData.js @@ -0,0 +1 @@ +export default { profileError: '', passwordError: '', oldPasswordError: false, newPasswordError: false, verifyPasswordError: false }; diff --git a/src/util/defaultTableState.js b/src/util/state/defaultTableState.js similarity index 100% rename from src/util/defaultTableState.js rename to src/util/state/defaultTableState.js diff --git a/src/util/cardOptions.js b/src/util/stripe/cardOptions.js similarity index 95% rename from src/util/cardOptions.js rename to src/util/stripe/cardOptions.js index dda83ff46..5d18da125 100644 --- a/src/util/cardOptions.js +++ b/src/util/stripe/cardOptions.js @@ -4,7 +4,7 @@ export default { base: { iconColor: '#403b8a', color: '#212121', - fontWeight: 500, + fontWeight: 100, fontFamily: 'Raleway, Open Sans, Segoe UI, sans-serif', fontSize: '16px', fontSmoothing: 'antialiased', diff --git a/src/util/customerHasChargeableCard.js b/src/util/stripe/customerHasChargeableCard.js similarity index 59% rename from src/util/customerHasChargeableCard.js rename to src/util/stripe/customerHasChargeableCard.js index 52e3f8fd9..0ad8a6477 100644 --- a/src/util/customerHasChargeableCard.js +++ b/src/util/stripe/customerHasChargeableCard.js @@ -1 +1 @@ -export default (customer) => customer?.stripe_customer_object?.sources?.data?.[0]?.status === 'chargeable'; +export default (customer) => customer?.stripe_customer_object?.sources?.data?.[0]; diff --git a/src/util/stripePromise.js b/src/util/stripe/stripePromise.js similarity index 100% rename from src/util/stripePromise.js rename to src/util/stripe/stripePromise.js diff --git a/src/util/updateInstanceStates.js b/src/util/updateInstanceStates.js deleted file mode 100644 index a0ce75d21..000000000 --- a/src/util/updateInstanceStates.js +++ /dev/null @@ -1,6 +0,0 @@ -export default (instances, authObject) => { - const existingAuthObjectIndex = instances.findIndex((i) => i.auth === authObject.auth && i.url === authObject.url); - if (existingAuthObjectIndex !== -1) instances.splice(existingAuthObjectIndex, 1); - const newInstancesObject = [...instances.map((i) => { i.active = false; return i; }), { ...authObject, active: true }].sort((a, b) => (a.url.toLowerCase() > b.url.toLowerCase() ? 1 : -1)); - return newInstancesObject; -}; diff --git a/src/util/useWindowSize.js b/src/util/useWindowSize.js deleted file mode 100644 index 7e056897e..000000000 --- a/src/util/useWindowSize.js +++ /dev/null @@ -1,19 +0,0 @@ -import { useState, useEffect } from 'react'; - -export default () => { - const [width, setWidth] = useState(window.innerWidth); - const [height, setHeight] = useState(window.innerHeight); - - useEffect(() => { - const handleResize = () => { - setWidth(window.innerWidth); - setHeight(window.innerHeight - 130); - }; - window.addEventListener('resize', handleResize); - return () => { - window.removeEventListener('resize', handleResize); - }; - }); - - return { width, height }; -}; From 95b4a1337aaf9495fd50d2a879719d687e7f919d Mon Sep 17 00:00:00 2001 From: deliciousmonster Date: Tue, 25 Feb 2020 16:43:23 -0700 Subject: [PATCH 13/19] instance config, add user --- mock_data/LMS_API.customer.json | 2 +- mock_data/LMS_API.customer_users.json | 37 ++++ mock_data/LMS_API.instances.json | 22 +-- mock_data/LMS_API.licenses.json | 90 +++++++++ mock_data/LMS_API.user.json | 10 +- package-lock.json | 12 +- package.json | 4 +- .../instance/addNode.js} | 2 +- .../instance/clusterStatus.js} | 2 +- .../instance/configureCluster.js} | 2 +- .../instance/createClusterUser.js | 2 +- .../instance/createClusterUserRole.js | 2 +- src/{util => api}/instance/csvDataLoad.js | 2 +- src/api/instance/getFingerprint.js | 6 + src/{util => api}/instance/getTableData.js | 2 +- src/{util => api}/instance/getTotalRecords.js | 2 +- src/api/instance/setLicense.js | 3 + .../instance/updateNode.js} | 2 +- src/{util => api}/lms/addInstance.js | 2 +- src/api/lms/addLicense.js | 8 + src/{util => api}/lms/addPaymentMethod.js | 2 +- src/api/lms/addTCAcceptance.js | 8 + src/api/lms/addUser.js | 8 + src/{util => api}/lms/getCustomer.js | 2 +- src/{util => api}/lms/getInstances.js | 2 +- .../lms/getInvoices.js} | 4 +- src/api/lms/getLicenses.js | 7 + src/api/lms/getProducts.js | 14 ++ src/api/lms/getRegions.js | 7 + src/{util => api}/lms/getUser.js | 2 +- src/api/lms/getUsers.js | 7 + src/api/lms/removeInstance.js | 8 + src/{util => api}/lms/removePaymentMethod.js | 2 +- src/api/lms/removeUser.js | 12 ++ src/api/lms/updateInstance.js | 8 + src/api/lms/updateLicense.js | 8 + .../queryInstance.js} | 0 src/api/queryLMS.js | 67 +++++++ src/app.scss | 1 - src/assets/styles/_base.scss | 2 +- src/assets/styles/_card.scss | 3 +- src/assets/styles/_db-browser.scss | 2 +- src/assets/styles/_dropdown.scss | 60 ------ src/assets/styles/_forms.scss | 34 ++++ src/assets/styles/_login.scss | 6 + src/assets/styles/_modal.scss | 38 +--- src/assets/styles/_react-table.scss | 3 +- src/assets/styles/_vars.scss | 4 +- src/components/account/billing_cardEdit.js | 7 +- src/components/account/billing_cardStatic.js | 2 +- src/components/account/billing_invoices.js | 4 +- src/components/account/profile.js | 178 +++--------------- src/components/account/profile_password.js | 76 ++++++++ src/components/account/profile_profile.js | 79 ++++++++ src/components/account/subscription.js | 15 -- src/components/account/users.js | 47 +++-- src/components/account/users_add.js | 81 ++++++++ src/components/account/users_datatable.js | 42 +++++ src/components/instance/browse/csvuploader.js | 4 +- src/components/instance/browse/datatable.js | 2 +- src/components/instance/browse/index.js | 4 +- src/components/instance/browse/jsonviewer.js | 2 +- src/components/instance/clustering/manage.js | 7 +- .../instance/clustering/setup_enable.js | 4 +- .../instance/clustering/setup_role.js | 2 +- .../instance/clustering/setup_user.js | 2 +- src/components/instance/config/index.js | 22 +++ .../instance/config/instanceDetails.js | 115 +++++++++++ .../instance/config/updateInstanceForm.js | 84 +++++++++ src/components/instance/index.js | 19 +- src/components/instance/license/index.js | 15 -- src/components/instances/cardBack.js | 2 +- src/components/instances/cloudInstanceForm.js | 12 ++ src/components/instances/index.js | 8 +- src/components/instances/newInstanceModal.js | 18 +- src/components/login.js | 7 +- src/components/shared/entityManagerForm.js | 2 +- src/components/shared/entityManagerRow.js | 2 +- src/routes/account.js | 4 +- src/routes/instance.js | 4 +- src/util/buildActiveInstanceObject.js | 19 ++ ...eEntities.js => buildInstanceStructure.js} | 0 .../browseTableColumns.js} | 2 +- src/util/datatable/clusterConfigColumns.js | 10 +- src/util/datatable/customerUserColumns.js | 28 +++ .../instance/buildActiveInstanceObject.js | 15 -- src/util/isEmail.js | 4 + src/util/lms/_queryLMS.js | 49 ----- src/util/lms/getProductsAndRegions.js | 20 -- src/util/state/defaultActiveInstance.js | 8 + src/util/state/defaultAuthFormData.js | 7 +- src/util/state/defaultUserFormData.js | 7 + .../{instance => stripe}/getInstancePrice.js | 0 93 files changed, 1099 insertions(+), 477 deletions(-) create mode 100644 mock_data/LMS_API.customer_users.json create mode 100644 mock_data/LMS_API.licenses.json rename src/{util/instance/connectToInstance.js => api/instance/addNode.js} (87%) rename src/{util/instance/getInstanceClusterStatus.js => api/instance/clusterStatus.js} (96%) rename src/{util/instance/enableInstanceClustering.js => api/instance/configureCluster.js} (90%) rename src/{util => api}/instance/createClusterUser.js (86%) rename src/{util => api}/instance/createClusterUserRole.js (86%) rename src/{util => api}/instance/csvDataLoad.js (76%) create mode 100644 src/api/instance/getFingerprint.js rename src/{util => api}/instance/getTableData.js (97%) rename src/{util => api}/instance/getTotalRecords.js (87%) create mode 100644 src/api/instance/setLicense.js rename src/{util/instance/updateInstanceSubscription.js => api/instance/updateNode.js} (96%) rename src/{util => api}/lms/addInstance.js (77%) create mode 100644 src/api/lms/addLicense.js rename src/{util => api}/lms/addPaymentMethod.js (78%) create mode 100644 src/api/lms/addTCAcceptance.js create mode 100644 src/api/lms/addUser.js rename src/{util => api}/lms/getCustomer.js (74%) rename src/{util => api}/lms/getInstances.js (74%) rename src/{util/lms/getCustomerInvoices.js => api/lms/getInvoices.js} (52%) create mode 100644 src/api/lms/getLicenses.js create mode 100644 src/api/lms/getProducts.js create mode 100644 src/api/lms/getRegions.js rename src/{util => api}/lms/getUser.js (73%) create mode 100644 src/api/lms/getUsers.js create mode 100644 src/api/lms/removeInstance.js rename src/{util => api}/lms/removePaymentMethod.js (78%) create mode 100644 src/api/lms/removeUser.js create mode 100644 src/api/lms/updateInstance.js create mode 100644 src/api/lms/updateLicense.js rename src/{util/instance/_queryInstance.js => api/queryInstance.js} (100%) create mode 100644 src/api/queryLMS.js delete mode 100644 src/assets/styles/_dropdown.scss create mode 100644 src/components/account/profile_password.js create mode 100644 src/components/account/profile_profile.js delete mode 100644 src/components/account/subscription.js create mode 100644 src/components/account/users_add.js create mode 100644 src/components/account/users_datatable.js create mode 100644 src/components/instance/config/index.js create mode 100644 src/components/instance/config/instanceDetails.js create mode 100644 src/components/instance/config/updateInstanceForm.js delete mode 100644 src/components/instance/license/index.js create mode 100644 src/util/buildActiveInstanceObject.js rename src/util/{instance/setStructureEntities.js => buildInstanceStructure.js} (100%) rename src/util/{instance/getInstanceDataStructure.js => datatable/browseTableColumns.js} (94%) create mode 100644 src/util/datatable/customerUserColumns.js delete mode 100644 src/util/instance/buildActiveInstanceObject.js create mode 100644 src/util/isEmail.js delete mode 100644 src/util/lms/_queryLMS.js delete mode 100644 src/util/lms/getProductsAndRegions.js create mode 100644 src/util/state/defaultActiveInstance.js create mode 100644 src/util/state/defaultUserFormData.js rename src/util/{instance => stripe}/getInstancePrice.js (100%) diff --git a/mock_data/LMS_API.customer.json b/mock_data/LMS_API.customer.json index 630f8e261..f36258fa6 100644 --- a/mock_data/LMS_API.customer.json +++ b/mock_data/LMS_API.customer.json @@ -1,5 +1,5 @@ { - "id": "guid", + "id": "customer-guid-1", "name": "customer name", "customer_hubspot_id": "guid", "stripe_customer_object": { diff --git a/mock_data/LMS_API.customer_users.json b/mock_data/LMS_API.customer_users.json new file mode 100644 index 000000000..ab97e24a1 --- /dev/null +++ b/mock_data/LMS_API.customer_users.json @@ -0,0 +1,37 @@ +[ + { + "user_id": "user-guid-1", + "firstname": "First1", + "lastname": "Last1", + "email": "user1@harperdb.io", + "customer_id": "customer-id-guid" + }, + { + "user_id": "user-guid-2", + "firstname": "First2", + "lastname": "Last2", + "email": "user2@harperdb.io", + "customer_id": "customer-id-guid" + }, + { + "user_id": "user-guid-3", + "firstname": "First3", + "lastname": "Last3", + "email": "user3@harperdb.io", + "customer_id": "customer-id-guid" + }, + { + "user_id": "user-guid-4", + "firstname": "First4", + "lastname": "Last4", + "email": "user4@harperdb.io", + "customer_id": "customer-id-guid" + }, + { + "user_id": "user-guid-5", + "firstname": "First5", + "lastname": "Last5", + "email": "user5@harperdb.io", + "customer_id": "customer-id-guid" + } +] diff --git a/mock_data/LMS_API.instances.json b/mock_data/LMS_API.instances.json index 17694b38f..47b1e3b73 100644 --- a/mock_data/LMS_API.instances.json +++ b/mock_data/LMS_API.instances.json @@ -8,11 +8,11 @@ "is_ssl": false, "is_local": true, "status": "ready", - "instance_region": "us-east", - "domain_name": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df.cloud.harperdb.io", - "elastic_ip_address": "172.168.2.12", + "instance_region": null, + "domain_name": null, + "elastic_ip_address": null, "customer_id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", - "stripe_product_id": "product_123412341234" + "stripe_product_id": "product_1" }, { "id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20de", @@ -27,7 +27,7 @@ "domain_name": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df.cloud.harperdb.io", "elastic_ip_address": "172.168.2.12", "customer_id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", - "stripe_product_id": "product_123412341234" + "stripe_product_id": "product_1" }, { "id": "cee98941-8975-44d6-88e6-9a1a4fa02b25", @@ -42,7 +42,7 @@ "domain_name": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df.cloud.harperdb.io", "elastic_ip_address": "172.168.2.12", "customer_id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", - "stripe_product_id": "product_123412341234" + "stripe_product_id": "product_1" }, { "id": "a32b2464-b5ca-4b9d-a10f-c6da23a7dcbe", @@ -57,7 +57,7 @@ "domain_name": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df.cloud.harperdb.io", "elastic_ip_address": "172.168.2.12", "customer_id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", - "stripe_product_id": "product_123412341234" + "stripe_product_id": "product_1" }, { "id": "7a60b51d-7983-4659-a34b-cfb578a4db54", @@ -72,7 +72,7 @@ "domain_name": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df.cloud.harperdb.io", "elastic_ip_address": "172.168.2.12", "customer_id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", - "stripe_product_id": "product_123412341234" + "stripe_product_id": "product_1" }, { "id": "f0d29404-9fb1-4d66-ab98-41f05e1ac665", @@ -87,7 +87,7 @@ "domain_name": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df.cloud.harperdb.io", "elastic_ip_address": "172.168.2.12", "customer_id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", - "stripe_product_id": "product_123412341234" + "stripe_product_id": "product_1" }, { "id": "ccf2b958-0b0e-4bf6-a81d-6abee1e4eec5", @@ -102,7 +102,7 @@ "domain_name": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df.cloud.harperdb.io", "elastic_ip_address": "172.168.2.12", "customer_id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", - "stripe_product_id": "product_123412341234" + "stripe_product_id": "product_1" }, { "id": "8291c224-6911-4155-b5a9-34559e5a45ff", @@ -117,6 +117,6 @@ "domain_name": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df.cloud.harperdb.io", "elastic_ip_address": "172.168.2.12", "customer_id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", - "stripe_product_id": "product_123412341234" + "stripe_product_id": "product_1" } ] diff --git a/mock_data/LMS_API.licenses.json b/mock_data/LMS_API.licenses.json new file mode 100644 index 000000000..5f3c0ab13 --- /dev/null +++ b/mock_data/LMS_API.licenses.json @@ -0,0 +1,90 @@ +[ + { + "license_id": "license-guid-0", + "instance_id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", + "customer_id": "customer-id-guid", + "stripe_product_id": "product_1", + "version": "2.0.14-BETA", + "exp_date": 1582669908353, + "storage_type": "LMDB", + "ram_allocation": 1, + "fingerprint": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df" + }, + { + "license_id": "license-guid-1", + "instance_id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20de", + "customer_id": "customer-id-guid", + "stripe_product_id": "product_1", + "version": "2.0.14-BETA", + "exp_date": 1582669908353, + "storage_type": "LMDB", + "ram_allocation": 1, + "fingerprint": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df" + }, + { + "license_id": "license-guid-2", + "instance_id": "cee98941-8975-44d6-88e6-9a1a4fa02b25", + "customer_id": "customer-id-guid", + "stripe_product_id": "product_1", + "version": "2.0.14-BETA", + "exp_date": 1582669908353, + "storage_type": "LMDB", + "ram_allocation": 1, + "fingerprint": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df" + }, + { + "license_id": "license-guid-3", + "instance_id": "a32b2464-b5ca-4b9d-a10f-c6da23a7dcbe", + "customer_id": "customer-id-guid", + "stripe_product_id": "product_1", + "version": "2.0.14-BETA", + "exp_date": 1582669908353, + "storage_type": "LMDB", + "ram_allocation": 1, + "fingerprint": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df" + }, + { + "license_id": "license-guid-4", + "instance_id": "7a60b51d-7983-4659-a34b-cfb578a4db54", + "customer_id": "customer-id-guid", + "stripe_product_id": "product_1", + "version": "2.0.14-BETA", + "exp_date": 1582669908353, + "storage_type": "LMDB", + "ram_allocation": 1, + "fingerprint": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df" + }, + { + "license_id": "license-guid-5", + "instance_id": "f0d29404-9fb1-4d66-ab98-41f05e1ac665", + "customer_id": "customer-id-guid", + "stripe_product_id": "product_1", + "version": "2.0.14-BETA", + "exp_date": 1582669908353, + "storage_type": "LMDB", + "ram_allocation": 1, + "fingerprint": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df" + }, + { + "license_id": "license-guid-6", + "instance_id": "ccf2b958-0b0e-4bf6-a81d-6abee1e4eec5", + "customer_id": "customer-id-guid", + "stripe_product_id": "product_1", + "version": "2.0.14-BETA", + "exp_date": 1582669908353, + "storage_type": "LMDB", + "ram_allocation": 1, + "fingerprint": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df" + }, + { + "license_id": "license-guid-7", + "instance_id": "8291c224-6911-4155-b5a9-34559e5a45ff", + "customer_id": "customer-id-guid", + "stripe_product_id": "product_1", + "version": "2.0.14-BETA", + "exp_date": 1582669908353, + "storage_type": "LMDB", + "ram_allocation": 1, + "fingerprint": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df" + } +] diff --git a/mock_data/LMS_API.user.json b/mock_data/LMS_API.user.json index fbbcda93f..b74e9569d 100644 --- a/mock_data/LMS_API.user.json +++ b/mock_data/LMS_API.user.json @@ -1,7 +1,7 @@ { - "id": "guid", - "firstname": "Jaxon", - "lastname": "Repp", - "email": "jaxon@harperdb.io", - "customer_id": "guid" + "id": "user-id-guid1", + "firstname": "First1", + "lastname": "Last1", + "email": "user1@harperdb.io", + "customer_id": "customer-id-guid" } diff --git a/package-lock.json b/package-lock.json index 0441555ad..d5167e8fc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3037,17 +3037,17 @@ "integrity": "sha512-vTCdPp/T/Q3oSqwHmZ5Kpa9oI7iLtGl3RQaA/NyLHikvcrPxACkkKVr/XzkSPJWXHRhKGzVvb0urJsbMlRxi1Q==" }, "@stripe/react-stripe-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@stripe/react-stripe-js/-/react-stripe-js-1.0.2.tgz", - "integrity": "sha512-xqPtVc6OLWJifWzEfn7hScYn9na0XHjtjKXNfqatqSbL+rxJ0JGAUyUYpRXGDdw/9DlLLZCYjIpgyxiqwnXq5g==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@stripe/react-stripe-js/-/react-stripe-js-1.0.3.tgz", + "integrity": "sha512-9E0HRAyd95B1jSS+nh5ol4M5RvQUnWTCB+Td9xKKa68OrEvmRqpNy/wvMyY9SGCy7fcMkZfiST9Oy09OkkDM/A==", "requires": { "prop-types": "^15.7.2" } }, "@stripe/stripe-js": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-1.0.1.tgz", - "integrity": "sha512-R6G3nBeMpT62+itLyn0uLIiDF3jqe7PqbCwGJtPYN5JBVczJGU9ZkOqICwfUQTSwIiL7kom9FgIa8vmO4MpwQQ==" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-1.0.2.tgz", + "integrity": "sha512-YZNXVFCDSvjjE7aPP78qUt4cBrHlrj97Q/uXgrZbjDwJdPJYXv31EQGgBblbDjdIeq+kPfmZixulc/Hn1LZqJQ==" }, "@testing-library/dom": { "version": "6.11.0", diff --git a/package.json b/package.json index bf5f390eb..a9015aa1b 100644 --- a/package.json +++ b/package.json @@ -15,8 +15,8 @@ "license": "Apache-2.0", "dependencies": { "@nio/ui-kit": "^2.2.2", - "@stripe/react-stripe-js": "^1.0.2", - "@stripe/stripe-js": "^1.0.1", + "@stripe/react-stripe-js": "^1.0.3", + "@stripe/stripe-js": "^1.0.2", "font-awesome": "^4.7.0", "json-stringify-pretty-compact": "^2.0.0", "react": "^16.12.0", diff --git a/src/util/instance/connectToInstance.js b/src/api/instance/addNode.js similarity index 87% rename from src/util/instance/connectToInstance.js rename to src/api/instance/addNode.js index 3957a05ce..f6baf4da4 100644 --- a/src/util/instance/connectToInstance.js +++ b/src/api/instance/addNode.js @@ -1,4 +1,4 @@ -import queryInstance from './_queryInstance'; +import queryInstance from '../queryInstance'; export default async ({ id, host, clusterPort, auth, refreshInstance }) => { await queryInstance({ operation: 'remove_node', name: id }, auth); diff --git a/src/util/instance/getInstanceClusterStatus.js b/src/api/instance/clusterStatus.js similarity index 96% rename from src/util/instance/getInstanceClusterStatus.js rename to src/api/instance/clusterStatus.js index 869065fce..79afa7eac 100644 --- a/src/util/instance/getInstanceClusterStatus.js +++ b/src/api/instance/clusterStatus.js @@ -1,4 +1,4 @@ -import queryInstance from './_queryInstance'; +import queryInstance from '../queryInstance'; const processConnections = (connections) => (connections ? connections.filter((c) => c.host_address.indexOf('::ffff') === -1).map((c) => ({ name: c.node_name, diff --git a/src/util/instance/enableInstanceClustering.js b/src/api/instance/configureCluster.js similarity index 90% rename from src/util/instance/enableInstanceClustering.js rename to src/api/instance/configureCluster.js index 52420e230..d2425181f 100644 --- a/src/util/instance/enableInstanceClustering.js +++ b/src/api/instance/configureCluster.js @@ -1,4 +1,4 @@ -import queryInstance from './_queryInstance'; +import queryInstance from '../queryInstance'; export default async ({ instanceId, username, port, auth, refreshInstance }) => { const query = { diff --git a/src/util/instance/createClusterUser.js b/src/api/instance/createClusterUser.js similarity index 86% rename from src/util/instance/createClusterUser.js rename to src/api/instance/createClusterUser.js index d221e7e41..7fe33aa9b 100644 --- a/src/util/instance/createClusterUser.js +++ b/src/api/instance/createClusterUser.js @@ -1,4 +1,4 @@ -import queryInstance from './_queryInstance'; +import queryInstance from '../queryInstance'; export default async ({ username, password, role, auth, refreshInstance }) => { const query = { diff --git a/src/util/instance/createClusterUserRole.js b/src/api/instance/createClusterUserRole.js similarity index 86% rename from src/util/instance/createClusterUserRole.js rename to src/api/instance/createClusterUserRole.js index 568bbbca3..f8d11bc5f 100644 --- a/src/util/instance/createClusterUserRole.js +++ b/src/api/instance/createClusterUserRole.js @@ -1,4 +1,4 @@ -import queryInstance from './_queryInstance'; +import queryInstance from '../queryInstance'; export default async ({ auth, refreshInstance }) => { const query = { diff --git a/src/util/instance/csvDataLoad.js b/src/api/instance/csvDataLoad.js similarity index 76% rename from src/util/instance/csvDataLoad.js rename to src/api/instance/csvDataLoad.js index e00b8eab1..31ba97bd8 100644 --- a/src/util/instance/csvDataLoad.js +++ b/src/api/instance/csvDataLoad.js @@ -1,3 +1,3 @@ -import queryInstance from './_queryInstance'; +import queryInstance from '../queryInstance'; export default async ({ schema, table, data, auth }) => queryInstance({ operation: 'csv_data_load', action: 'insert', schema, table, data }, auth); diff --git a/src/api/instance/getFingerprint.js b/src/api/instance/getFingerprint.js new file mode 100644 index 000000000..37e7614b3 --- /dev/null +++ b/src/api/instance/getFingerprint.js @@ -0,0 +1,6 @@ +import queryInstance from '../queryInstance'; + +export default async ({ auth }) => { + const { message } = await queryInstance({ operation: 'get_fingerprint' }, auth); + return message; +}; diff --git a/src/util/instance/getTableData.js b/src/api/instance/getTableData.js similarity index 97% rename from src/util/instance/getTableData.js rename to src/api/instance/getTableData.js index 4b127375e..02f5b023a 100644 --- a/src/util/instance/getTableData.js +++ b/src/api/instance/getTableData.js @@ -1,4 +1,4 @@ -import queryInstance from './_queryInstance'; +import queryInstance from '../queryInstance'; export default async ({ schema, table, tableState, auth }) => { if (!tableState.sorted.length) return false; diff --git a/src/util/instance/getTotalRecords.js b/src/api/instance/getTotalRecords.js similarity index 87% rename from src/util/instance/getTotalRecords.js rename to src/api/instance/getTotalRecords.js index 2afaf3aa9..a6307ceec 100644 --- a/src/util/instance/getTotalRecords.js +++ b/src/api/instance/getTotalRecords.js @@ -1,4 +1,4 @@ -import queryInstance from './_queryInstance'; +import queryInstance from '../queryInstance'; export default async ({ schema, table, auth }) => { let newTotalRecords = 0; diff --git a/src/api/instance/setLicense.js b/src/api/instance/setLicense.js new file mode 100644 index 000000000..0d89a4656 --- /dev/null +++ b/src/api/instance/setLicense.js @@ -0,0 +1,3 @@ +import queryInstance from '../queryInstance'; + +export default async ({ auth, key, company }) => queryInstance({ operation: 'set_license', key, company }, auth); diff --git a/src/util/instance/updateInstanceSubscription.js b/src/api/instance/updateNode.js similarity index 96% rename from src/util/instance/updateInstanceSubscription.js rename to src/api/instance/updateNode.js index 759e1a04a..3891475ca 100644 --- a/src/util/instance/updateInstanceSubscription.js +++ b/src/api/instance/updateNode.js @@ -1,4 +1,4 @@ -import queryInstance from './_queryInstance'; +import queryInstance from '../queryInstance'; export default async ({ channel, subscriptions, buttonState, id, host, clusterPort, auth, refreshInstance }) => { const existingChannelSubscriptionIndex = subscriptions.findIndex((s) => s.channel === channel); diff --git a/src/util/lms/addInstance.js b/src/api/lms/addInstance.js similarity index 77% rename from src/util/lms/addInstance.js rename to src/api/lms/addInstance.js index dbd5a376f..2cd12f334 100644 --- a/src/util/lms/addInstance.js +++ b/src/api/lms/addInstance.js @@ -1,4 +1,4 @@ -import queryLMS from './_queryLMS'; +import queryLMS from '../queryLMS'; export default async ({ auth, payload }) => queryLMS({ endpoint: 'addInstance', diff --git a/src/api/lms/addLicense.js b/src/api/lms/addLicense.js new file mode 100644 index 000000000..fa4e6b99c --- /dev/null +++ b/src/api/lms/addLicense.js @@ -0,0 +1,8 @@ +import queryLMS from '../queryLMS'; + +export default async ({ auth, payload: { instance_id, customer_id, stripe_product_id, fingerprint } }) => queryLMS({ + endpoint: 'addLicense', + method: 'POST', + payload: { instance_id, customer_id, stripe_product_id, fingerprint }, + auth, +}); diff --git a/src/util/lms/addPaymentMethod.js b/src/api/lms/addPaymentMethod.js similarity index 78% rename from src/util/lms/addPaymentMethod.js rename to src/api/lms/addPaymentMethod.js index 4ed8be754..7d4761825 100644 --- a/src/util/lms/addPaymentMethod.js +++ b/src/api/lms/addPaymentMethod.js @@ -1,4 +1,4 @@ -import queryLMS from './_queryLMS'; +import queryLMS from '../queryLMS'; export default async ({ auth, payload }) => queryLMS({ endpoint: 'addPaymentMethod', diff --git a/src/api/lms/addTCAcceptance.js b/src/api/lms/addTCAcceptance.js new file mode 100644 index 000000000..c8e7f0bdc --- /dev/null +++ b/src/api/lms/addTCAcceptance.js @@ -0,0 +1,8 @@ +import queryLMS from '../queryLMS'; + +export default async ({ auth, payload }) => queryLMS({ + endpoint: 'addTCAcceptance', + method: 'POST', + payload, + auth, +}); diff --git a/src/api/lms/addUser.js b/src/api/lms/addUser.js new file mode 100644 index 000000000..dd67d5cfe --- /dev/null +++ b/src/api/lms/addUser.js @@ -0,0 +1,8 @@ +import queryLMS from '../queryLMS'; + +export default async ({ auth, payload }) => queryLMS({ + endpoint: 'addUser', + method: 'POST', + payload, + auth, +}); diff --git a/src/util/lms/getCustomer.js b/src/api/lms/getCustomer.js similarity index 74% rename from src/util/lms/getCustomer.js rename to src/api/lms/getCustomer.js index cf042514e..5f0436c30 100644 --- a/src/util/lms/getCustomer.js +++ b/src/api/lms/getCustomer.js @@ -1,4 +1,4 @@ -import queryLMS from './_queryLMS'; +import queryLMS from '../queryLMS'; export default async ({ auth }) => queryLMS({ endpoint: 'getCustomer', diff --git a/src/util/lms/getInstances.js b/src/api/lms/getInstances.js similarity index 74% rename from src/util/lms/getInstances.js rename to src/api/lms/getInstances.js index 6748f3d7c..8cfec0985 100644 --- a/src/util/lms/getInstances.js +++ b/src/api/lms/getInstances.js @@ -1,4 +1,4 @@ -import queryLMS from './_queryLMS'; +import queryLMS from '../queryLMS'; export default async ({ auth }) => queryLMS({ endpoint: 'getInstances', diff --git a/src/util/lms/getCustomerInvoices.js b/src/api/lms/getInvoices.js similarity index 52% rename from src/util/lms/getCustomerInvoices.js rename to src/api/lms/getInvoices.js index 988f1d536..ab5c1402e 100644 --- a/src/util/lms/getCustomerInvoices.js +++ b/src/api/lms/getInvoices.js @@ -1,7 +1,7 @@ -import queryLMS from './_queryLMS'; +import queryLMS from '../queryLMS'; export default async ({ auth }) => queryLMS({ - endpoint: 'getCustomerInvoices', + endpoint: 'getInvoices', method: 'POST', auth, }); diff --git a/src/api/lms/getLicenses.js b/src/api/lms/getLicenses.js new file mode 100644 index 000000000..47d26e12a --- /dev/null +++ b/src/api/lms/getLicenses.js @@ -0,0 +1,7 @@ +import queryLMS from '../queryLMS'; + +export default async ({ auth }) => queryLMS({ + endpoint: 'getLicenses', + method: 'POST', + auth, +}); diff --git a/src/api/lms/getProducts.js b/src/api/lms/getProducts.js new file mode 100644 index 000000000..03c49bde2 --- /dev/null +++ b/src/api/lms/getProducts.js @@ -0,0 +1,14 @@ +import queryLMS from '../queryLMS'; + +export default async ({ auth }) => { + const newProducts = await queryLMS({ + endpoint: 'getProducts', + method: 'POST', + auth, + }); + return { + raw: newProducts, + local: newProducts.map((p) => ({ label: `${p.instance_ram}GB RAM | ${p.instance_disk_space_gigs}GB Disk Space | ${p.local_price_annual ? `$${p.local_price_annual}/yr` : 'FREE'}`, value: p.stripe_product_id })), + cloud: newProducts.map((p) => ({ label: `${p.instance_ram}GB RAM | ${p.instance_disk_space_gigs}GB Disk Space | ${p.cloud_price_monthly ? `$${p.cloud_price_monthly}/mo` : 'FREE'}`, value: p.stripe_product_id })), + }; +}; diff --git a/src/api/lms/getRegions.js b/src/api/lms/getRegions.js new file mode 100644 index 000000000..f83e68e25 --- /dev/null +++ b/src/api/lms/getRegions.js @@ -0,0 +1,7 @@ +import queryLMS from '../queryLMS'; + +export default async ({ auth }) => queryLMS({ + endpoint: 'getRegions', + method: 'POST', + auth, +}); diff --git a/src/util/lms/getUser.js b/src/api/lms/getUser.js similarity index 73% rename from src/util/lms/getUser.js rename to src/api/lms/getUser.js index a860cf02a..f10609203 100644 --- a/src/util/lms/getUser.js +++ b/src/api/lms/getUser.js @@ -1,4 +1,4 @@ -import queryLMS from './_queryLMS'; +import queryLMS from '../queryLMS'; export default async ({ auth }) => queryLMS({ endpoint: 'getUser', diff --git a/src/api/lms/getUsers.js b/src/api/lms/getUsers.js new file mode 100644 index 000000000..d0420e196 --- /dev/null +++ b/src/api/lms/getUsers.js @@ -0,0 +1,7 @@ +import queryLMS from '../queryLMS'; + +export default async ({ auth }) => queryLMS({ + endpoint: 'getUsers', + method: 'POST', + auth, +}); diff --git a/src/api/lms/removeInstance.js b/src/api/lms/removeInstance.js new file mode 100644 index 000000000..0b0e8ebd2 --- /dev/null +++ b/src/api/lms/removeInstance.js @@ -0,0 +1,8 @@ +import queryLMS from '../queryLMS'; + +export default async ({ auth, payload }) => queryLMS({ + endpoint: 'removeInstance', + method: 'POST', + payload, + auth, +}); diff --git a/src/util/lms/removePaymentMethod.js b/src/api/lms/removePaymentMethod.js similarity index 78% rename from src/util/lms/removePaymentMethod.js rename to src/api/lms/removePaymentMethod.js index 6b8a96f45..c43dbcef8 100644 --- a/src/util/lms/removePaymentMethod.js +++ b/src/api/lms/removePaymentMethod.js @@ -1,4 +1,4 @@ -import queryLMS from './_queryLMS'; +import queryLMS from '../queryLMS'; export default async ({ auth, payload }) => queryLMS({ endpoint: 'removePaymentMethod', diff --git a/src/api/lms/removeUser.js b/src/api/lms/removeUser.js new file mode 100644 index 000000000..9f3015d49 --- /dev/null +++ b/src/api/lms/removeUser.js @@ -0,0 +1,12 @@ +import queryLMS from '../queryLMS'; + +export default async ({ auth, user_id, customer_id, setLastUpdate }) => { + const result = await queryLMS({ + endpoint: 'removeUser', + method: 'POST', + payload: { user_id, customer_id }, + auth, + }); + setLastUpdate(Date.now()); + return result; +}; diff --git a/src/api/lms/updateInstance.js b/src/api/lms/updateInstance.js new file mode 100644 index 000000000..9e4c0c188 --- /dev/null +++ b/src/api/lms/updateInstance.js @@ -0,0 +1,8 @@ +import queryLMS from '../queryLMS'; + +export default async ({ auth, instance_name, stripe_product_id, instance_id, customer_id }) => queryLMS({ + endpoint: 'updateInstance', + method: 'POST', + payload: { stripe_product_id, instance_id, customer_id }, + auth, +}); diff --git a/src/api/lms/updateLicense.js b/src/api/lms/updateLicense.js new file mode 100644 index 000000000..dbcb69c84 --- /dev/null +++ b/src/api/lms/updateLicense.js @@ -0,0 +1,8 @@ +import queryLMS from '../queryLMS'; + +export default async ({ auth, payload: { license_id, instance_id, customer_id, stripe_product_id, fingerprint } }) => queryLMS({ + endpoint: 'addLicense', + method: 'POST', + payload: { license_id, instance_id, customer_id, stripe_product_id, fingerprint }, + auth, +}); diff --git a/src/util/instance/_queryInstance.js b/src/api/queryInstance.js similarity index 100% rename from src/util/instance/_queryInstance.js rename to src/api/queryInstance.js diff --git a/src/api/queryLMS.js b/src/api/queryLMS.js new file mode 100644 index 000000000..9fb4b1d66 --- /dev/null +++ b/src/api/queryLMS.js @@ -0,0 +1,67 @@ +import instances from '../../mock_data/LMS_API.instances.json'; +import products from '../../mock_data/LMS_API.products.json'; +import regions from '../../mock_data/LMS_API.aws_regions.json'; +import customer from '../../mock_data/LMS_API.customer.json'; +import user from '../../mock_data/LMS_API.user.json'; +import customer_users from '../../mock_data/LMS_API.customer_users.json'; +import invoices from '../../mock_data/LMS_API.invoices.json'; +import licenses from '../../mock_data/LMS_API.licenses.json'; + +// eslint-disable-next-line no-unused-vars +export default async ({ endpoint, payload, auth }) => { + /* + const request = await fetch( + `https://api.harperdb.io/v1/${endpoint}`, + { + method: 'POST', + body: JSON.stringify(payload), + headers: { + 'Content-Type': 'application/json', + authorization: `Basic ${btoa(`${auth.user}:${auth.pass}`)}`, + }, + }, + ); + return request.json(); + */ + + console.log('Calling API', endpoint); + + switch (endpoint) { + case 'getProducts': + return products; + case 'getRegions': + return regions; + case 'getUser': + return user; + case 'getCustomer': + return customer; + case 'getInstances': + return instances; + case 'getUsers': + return customer_users; + case 'getLicenses': + return licenses; + case 'getInvoices': + return invoices; + case 'addLicense': + return { result: true, message: 'Created license', key: '78rfh334ofholhfdoeh3f48hfq', company: 'customer-guid-1' }; + case 'updateLicense': + return { result: true, message: 'Updated license', key: '78rfh334ofholhfdoeh3f48hfq', company: 'customer-guid-1' }; + case 'addTCAcceptance': + return { result: true, message: 'Received Terms and Conditions acceptance' }; + case 'addPaymentMethod': + return { result: true, message: 'Payment method added to account' }; + case 'removePaymentMethod': + return { result: true, message: 'Payment method removed from account' }; + case 'removeUser': + return { result: true, message: 'User removed from account' }; + case 'updateInstance': + return { result: true, message: 'Instance is being updated' }; + case 'addInstance': + return { result: true, message: 'Instance is being created||added', instance_id: 'instance-guid-1' }; + case 'addUser': + return { result: true, message: 'User created successfully', user_id: 'user-guid-6' }; + default: + return { error: 'unknown endpoint' }; + } +}; diff --git a/src/app.scss b/src/app.scss index dee1c3c1a..4786f1195 100644 --- a/src/app.scss +++ b/src/app.scss @@ -13,7 +13,6 @@ @import './assets/styles/divider'; @import './assets/styles/card'; @import './assets/styles/button'; -@import './assets/styles/dropdown'; @import './assets/styles/code'; // component styles diff --git a/src/assets/styles/_base.scss b/src/assets/styles/_base.scss index cb6bdd31b..83ba18c87 100644 --- a/src/assets/styles/_base.scss +++ b/src/assets/styles/_base.scss @@ -106,7 +106,7 @@ body { .nav-link, a { display: inline-block; - color: $light-white-overlay !important; + color: $white-overlay !important; padding: 5px 0 0 !important; &.active, diff --git a/src/assets/styles/_card.scss b/src/assets/styles/_card.scss index 4c3cc89da..a8be73280 100644 --- a/src/assets/styles/_card.scss +++ b/src/assets/styles/_card.scss @@ -1,7 +1,8 @@ .card { - background: $color-white !important; + background: $light-white-overlay !important; color: $color-black; border-radius: 4px !important; + box-shadow: 0 0 10px $grey-overlay; .card-body { padding: 15px; diff --git a/src/assets/styles/_db-browser.scss b/src/assets/styles/_db-browser.scss index 1340f1191..260ddcf3a 100644 --- a/src/assets/styles/_db-browser.scss +++ b/src/assets/styles/_db-browser.scss @@ -5,7 +5,7 @@ padding: 0 15px; .item-row { - border-bottom: 1px solid $color-purple; + border-bottom: 1px solid $light-grey-overlay; padding-top: 12px; padding-bottom: 6px; height: 49px; diff --git a/src/assets/styles/_dropdown.scss b/src/assets/styles/_dropdown.scss deleted file mode 100644 index 00d1d3b51..000000000 --- a/src/assets/styles/_dropdown.scss +++ /dev/null @@ -1,60 +0,0 @@ -#app-nav { - .dropdown { - width: 100%; - - &.nav-item { - width: 250px; - } - - span { - display: block; - text-overflow: ellipsis; - overflow: hidden; - padding-right: 15px; - white-space: nowrap; - } - - .dropdown-menu { - width: 100%; - border-radius: 6px; - background-color: $color-black !important; - z-index: 9999; - margin: 0; - padding: 0; - - .dropdown-item { - color: $color-white; - border-bottom: 1px solid $white-overlay; - padding: .5rem 1.5rem; - background-color: $color-black !important; - border-radius: 6px; - - span { - padding-right: 0; - } - - &:last-child { - border-bottom: 0; - } - - &:hover { - background-color: $color-black !important; - color: $color-white; - } - } - } - - .dropdown-toggle { - padding-left: 1.5rem; - width: 100%; - text-align: left; - position: relative; - - &::after { - position: absolute; - top: 15px; - right: 15px; - } - } - } -} diff --git a/src/assets/styles/_forms.scss b/src/assets/styles/_forms.scss index 195be0c0e..949621194 100644 --- a/src/assets/styles/_forms.scss +++ b/src/assets/styles/_forms.scss @@ -62,6 +62,40 @@ textarea { -moz-osx-font-smoothing: grayscale; } +.radio-button > div { + border-bottom: 1px solid $light-grey-overlay; + padding: 8px 0; + white-space: nowrap; + + &:last-child { + border-bottom: none; + padding-bottom: 4px; + } +} + +.radio-checkbox-label { + white-space: nowrap; + overflow-x: hidden; +} + +.new-instance-label { + font-weight: 700; + margin-bottom: 5px; +} + +.fieldset { + border-top: 1px solid $light-grey-overlay; + padding: 5px 0; + max-height: 129px; + margin-bottom: 25px; + overflow-x: hidden; + overflow-y: auto; + + &:last-child { + margin-bottom: 0; + } +} + diff --git a/src/assets/styles/_login.scss b/src/assets/styles/_login.scss index 4da2b2ca7..3c672a9e6 100644 --- a/src/assets/styles/_login.scss +++ b/src/assets/styles/_login.scss @@ -18,10 +18,16 @@ background-size: cover; } + .card { + background: $white-overlay !important; + box-shadow: none !important; + } + input { text-align: center !important; background: $white-overlay !important; color: $color-white !important; + border: none !important; &::placeholder { text-align: center !important; diff --git a/src/assets/styles/_modal.scss b/src/assets/styles/_modal.scss index 77ab3f392..687db53fc 100644 --- a/src/assets/styles/_modal.scss +++ b/src/assets/styles/_modal.scss @@ -1,12 +1,12 @@ #new-instance-modal { - .new-instance-label { - font-weight: 700; - margin-bottom: 5px; - } - .instance-form-card-holder { - .instance-form-card-body { - background: $light-grey-overlay; + + .card { + box-shadow: none !important; + + .instance-form-card-body { + background: $light-grey-overlay; + } } &:first-child{ @@ -15,28 +15,4 @@ } } } - - - .radio-button > div { - border-bottom: 1px solid $light-grey-overlay; - padding: 8px 0; - - &:last-child { - border-bottom: none; - padding-bottom: 4px; - } - } - - .fieldset { - border-top: 1px solid $light-grey-overlay; - padding: 5px 0; - max-height: 129px; - margin-bottom: 25px; - overflow-x: hidden; - overflow-y: auto; - - &:last-child { - margin-bottom: 0; - } - } } diff --git a/src/assets/styles/_react-table.scss b/src/assets/styles/_react-table.scss index b36cdc209..c446c7c48 100755 --- a/src/assets/styles/_react-table.scss +++ b/src/assets/styles/_react-table.scss @@ -11,8 +11,7 @@ &.-filters { input { - background: rgba(0, 0, 0, .2); - border: 0; + background: rgba(0, 0, 0, .2) !important; } } } diff --git a/src/assets/styles/_vars.scss b/src/assets/styles/_vars.scss index b22976a2a..154c3c098 100644 --- a/src/assets/styles/_vars.scss +++ b/src/assets/styles/_vars.scss @@ -15,8 +15,8 @@ $color-purple: #403b8a; $color-lightpurple: #7a3a87; $gradient-purple-magenta: linear-gradient(45deg, $color-darkpurple, $color-purple, $color-lightpurple); -$white-overlay: rgba(255, 255, 255, .3); -$light-white-overlay: rgba(255, 255, 255, .7); +$white-overlay: rgba(255, 255, 255, .5); +$light-white-overlay: rgba(255, 255, 255, .9); $light-grey-overlay: rgba(0, 0, 0, .05); $grey-overlay: rgba(0, 0, 0, .5); diff --git a/src/components/account/billing_cardEdit.js b/src/components/account/billing_cardEdit.js index e4bbf3df9..e1a2b3c8c 100644 --- a/src/components/account/billing_cardEdit.js +++ b/src/components/account/billing_cardEdit.js @@ -1,13 +1,13 @@ import React, { useState } from 'react'; -import { Button, Row, Col, Loader, Input } from '@nio/ui-kit'; +import { Button, Row, Col, Input } from '@nio/ui-kit'; import { CardNumberElement, CardExpiryElement, CardCvcElement, useElements, useStripe } from '@stripe/react-stripe-js'; import useAsyncEffect from 'use-async-effect'; import cardOptions from '../../util/stripe/cardOptions'; import useLMS from '../../stores/lmsData'; import defaultLMSData from '../../util/state/defaultLMSData'; -import addPaymentMethod from '../../util/lms/addPaymentMethod'; -import getCustomer from '../../util/lms/getCustomer'; +import addPaymentMethod from '../../api/lms/addPaymentMethod'; +import getCustomer from '../../api/lms/getCustomer'; export default ({ setEditingCard, customerCard }) => { const [lmsData, setLMSData] = useLMS(defaultLMSData); @@ -36,7 +36,6 @@ export default ({ setEditingCard, customerCard }) => { const payload = await stripe.createPaymentMethod(newCardObject); - console.log(payload); if (payload.error) { setError(payload.error); } else { diff --git a/src/components/account/billing_cardStatic.js b/src/components/account/billing_cardStatic.js index 5204c197c..e9c645f21 100644 --- a/src/components/account/billing_cardStatic.js +++ b/src/components/account/billing_cardStatic.js @@ -3,7 +3,7 @@ import useAsyncEffect from 'use-async-effect'; import { Row, Col, Button } from '@nio/ui-kit'; import { useAlert } from 'react-alert'; -import removePaymentMethod from '../../util/lms/removePaymentMethod'; +import removePaymentMethod from '../../api/lms/removePaymentMethod'; export default ({ customerId, customerCard, customerStripeId, setEditingCard, auth }) => { const [removing, setRemoving] = useState(false); diff --git a/src/components/account/billing_invoices.js b/src/components/account/billing_invoices.js index 2d176e4a7..f21408335 100644 --- a/src/components/account/billing_invoices.js +++ b/src/components/account/billing_invoices.js @@ -2,7 +2,7 @@ import React, { useState } from 'react'; import { Row, Col } from '@nio/ui-kit'; import useAsyncEffect from 'use-async-effect'; -import getCustomerInvoices from '../../util/lms/getCustomerInvoices'; +import getInvoices from '../../api/lms/getInvoices'; import useLMS from '../../stores/lmsData'; import defaultLMSData from '../../util/state/defaultLMSData'; @@ -11,7 +11,7 @@ export default () => { const [customerInvoices, setCustomerInvoices] = useState([]); useAsyncEffect(async () => { - const newCustomerInvoices = await getCustomerInvoices({ auth: lmsData.auth }); + const newCustomerInvoices = await getInvoices({ auth: lmsData.auth }); setCustomerInvoices(newCustomerInvoices); }, []); diff --git a/src/components/account/profile.js b/src/components/account/profile.js index f238d8ad3..851c308e6 100644 --- a/src/components/account/profile.js +++ b/src/components/account/profile.js @@ -1,156 +1,26 @@ -import React, { useState } from 'react'; -import { Row, Col, Input, Button, Card, CardBody } from '@nio/ui-kit'; -import useAsyncEffect from 'use-async-effect'; +import React from 'react'; +import { Row, Col, Card, CardBody } from '@nio/ui-kit'; -import defaultProfileFormData from '../../util/state/defaultProfileFormData'; +import ProfileForm from './profile_profile'; +import PasswordForm from './profile_password'; -export default () => { - const [passwordForm, updatePasswordForm] = useState(defaultProfileFormData); - const [profileForm, updateProfileForm] = useState(defaultProfileFormData); - - useAsyncEffect(async () => { - if (passwordForm.submitted) { - console.log(passwordForm); - // await updatePassword(passwordForm); - } - }, [passwordForm]); - - useAsyncEffect(async () => { - if (profileForm.submitted) { - console.log(profileForm); - // await updateProfile(profileForm); - } - }, [profileForm]); - - return ( - - - profile - - - - - first name - - - updateProfileForm({ ...profileForm, firstname: e.target.value, error: false })} - /> - - -
- - - last name - - - updateProfileForm({ ...profileForm, lastname: e.target.value, error: false })} - /> - - -
- - - email address - - - updateProfileForm({ ...profileForm, email: e.target.value, error: false })} - /> - - -
- {profileForm.error && ( -
- {profileForm.error} -
-
- )} - -
-
- - - password - - - - - current password - - - updatePasswordForm({ ...passwordForm, oldpassword: e.target.value, error: false })} - /> - - -
- - - new password - - - updatePasswordForm({ ...passwordForm, newpassword: e.target.value, error: false })} - /> - - -
- - - verify password - - - updatePasswordForm({ ...passwordForm, newpassword2: e.target.value, error: false })} - /> - - -
- {passwordForm.error && ( -
- {passwordForm.error} -
-
- )} - -
-
- -
- ); -}; +export default () => ( + + + profile + + + + + + + + password + + + + + + + +); diff --git a/src/components/account/profile_password.js b/src/components/account/profile_password.js new file mode 100644 index 000000000..2e98b67e7 --- /dev/null +++ b/src/components/account/profile_password.js @@ -0,0 +1,76 @@ +import React, { useState } from 'react'; +import { Row, Col, Input, Button } from '@nio/ui-kit'; +import useAsyncEffect from 'use-async-effect'; + +import defaultProfileFormData from '../../util/state/defaultProfileFormData'; + +export default () => { + const [passwordForm, updatePasswordForm] = useState(defaultProfileFormData); + + useAsyncEffect(async () => { + if (passwordForm.submitted) { + console.log(passwordForm); + // await updatePassword(passwordForm); + } + }, [passwordForm]); + + return ( + <> + + + current password + + + updatePasswordForm({ ...passwordForm, oldpassword: e.target.value, error: false })} + /> + + +
+ + + new password + + + updatePasswordForm({ ...passwordForm, newpassword: e.target.value, error: false })} + /> + + +
+ + + verify password + + + updatePasswordForm({ ...passwordForm, newpassword2: e.target.value, error: false })} + /> + + +
+ {passwordForm.error && ( +
+ {passwordForm.error} +
+
+ )} + + + ); +}; diff --git a/src/components/account/profile_profile.js b/src/components/account/profile_profile.js new file mode 100644 index 000000000..6c478b807 --- /dev/null +++ b/src/components/account/profile_profile.js @@ -0,0 +1,79 @@ +import React, { useState } from 'react'; +import { Row, Col, Input, Button } from '@nio/ui-kit'; +import useAsyncEffect from 'use-async-effect'; + +import defaultProfileFormData from '../../util/state/defaultProfileFormData'; + +export default () => { + const [profileForm, updateProfileForm] = useState(defaultProfileFormData); + + useAsyncEffect(async () => { + if (profileForm.submitted) { + console.log(profileForm); + // await updateProfile(profileForm); + } + }, [profileForm]); + + return ( + <> + + + first name + + + updateProfileForm({ ...profileForm, firstname: e.target.value, error: false })} + /> + + +
+ + + last name + + + updateProfileForm({ ...profileForm, lastname: e.target.value, error: false })} + /> + + +
+ + + email address + + + updateProfileForm({ ...profileForm, email: e.target.value, error: false })} + /> + + +
+ {profileForm.error && ( +
+ {profileForm.error} +
+
+ )} + + + ); +}; diff --git a/src/components/account/subscription.js b/src/components/account/subscription.js deleted file mode 100644 index 491693356..000000000 --- a/src/components/account/subscription.js +++ /dev/null @@ -1,15 +0,0 @@ -import React from 'react'; -import { Card, CardBody, Row, Col } from '@nio/ui-kit'; - -export default () => ( - - - subscription - - - content here - - - - -); diff --git a/src/components/account/users.js b/src/components/account/users.js index ef1d58070..de6630f02 100644 --- a/src/components/account/users.js +++ b/src/components/account/users.js @@ -1,15 +1,32 @@ -import React from 'react'; -import { Card, CardBody, Row, Col } from '@nio/ui-kit'; - -export default () => ( - - - users - - - content here - - - - -); +import React, { useState } from 'react'; +import { Row, Col } from '@nio/ui-kit'; +import useAsyncEffect from 'use-async-effect'; + +import DataTable from './users_datatable'; +import AddUserForm from './users_add'; +import getUsers from '../../api/lms/getUsers'; +import useLMS from '../../stores/lmsData'; +import defaultLMSData from '../../util/state/defaultLMSData'; +import customerUserColumns from '../../util/datatable/customerUserColumns'; + +export default () => { + const [{ auth, customer }] = useLMS(defaultLMSData); + const [lastUpdate, setLastUpdate] = useState(false); + const [tableData, setTableData] = useState({ data: [], columns: customerUserColumns({ auth, setLastUpdate, customer_id: customer?.id }) }); + + useAsyncEffect(async () => { + const newTableData = await getUsers({ auth }); + setTableData({ ...tableData, data: newTableData }); + }, [lastUpdate]); + + return ( + + + + + + + + + ); +}; diff --git a/src/components/account/users_add.js b/src/components/account/users_add.js new file mode 100644 index 000000000..667428d30 --- /dev/null +++ b/src/components/account/users_add.js @@ -0,0 +1,81 @@ +import React, { useState } from 'react'; +import { Input, Button, Card, CardBody } from '@nio/ui-kit'; +import useAsyncEffect from 'use-async-effect'; +import { useAlert } from 'react-alert'; + +import addUser from '../../api/lms/addUser'; +import defaultUserFormData from '../../util/state/defaultUserFormData'; +import isEmail from '../../util/isEmail'; + +export default ({ setLastUpdate, customerId }) => { + const alert = useAlert(); + const [userForm, updateUserForm] = useState(defaultUserFormData); + + useAsyncEffect(async () => { + if (userForm.submitted) { + userForm.customer_id = customerId; + if (!userForm.customer_id || !userForm.firstname || !userForm.lastname || !isEmail(userForm.email)) { + updateUserForm({ ...userForm, submitted: false, error: 'All fields must be filled out' }); + } else { + const response = await addUser(userForm); + if (response.result) { + updateUserForm(defaultUserFormData); + setLastUpdate(Date.now()); + alert.success(response.message); + } else { + alert.error(response.message); + } + } + } + }, [userForm]); + + return ( + <> + add user + + + updateUserForm({ ...userForm, firstname: e.target.value, error: false })} + /> +
+ updateUserForm({ ...userForm, lastname: e.target.value, error: false })} + /> +
+ updateUserForm({ ...userForm, email: e.target.value, error: false })} + /> +
+ + {userForm.error && ( +
+
+ {userForm.error} +
+ )} +
+
+ + ); +}; diff --git a/src/components/account/users_datatable.js b/src/components/account/users_datatable.js new file mode 100644 index 000000000..2bb7e7e7e --- /dev/null +++ b/src/components/account/users_datatable.js @@ -0,0 +1,42 @@ +import React, { useState } from 'react'; +import { Card, CardBody, Row, Col } from '@nio/ui-kit'; +import ReactTable from 'react-table'; + +import defaultTableState from '../../util/state/defaultTableState'; + +export default ({ tableData }) => { + const [tableState, setTableState] = useState({ ...defaultTableState, sorted: [{ id: 'lastname', desc: false }] }); + + return ( + <> + + + existing users + + + setTableState({ ...tableState, filtered: tableState.showFilter ? [] : tableState.filtered, showFilter: !tableState.showFilter })} /> + + + + + setTableState({ ...tableState, filtered: value })} + filtered={tableState.filtered} + onSortedChange={(value) => setTableState({ ...tableState, sorted: value })} + sorted={tableState.sorted} + onPageChange={(value) => setTableState({ ...tableState, page: value })} + page={tableState.page} + filterable={tableState.showFilter} + defaultPageSize={tableState.pageSize} + pageSize={tableState.pageSize} + onPageSizeChange={(value) => setTableState({ ...tableState, pageSize: value })} + resizable={false} + /> + + + + ); +}; diff --git a/src/components/instance/browse/csvuploader.js b/src/components/instance/browse/csvuploader.js index 4d0608fd1..b61d8a927 100644 --- a/src/components/instance/browse/csvuploader.js +++ b/src/components/instance/browse/csvuploader.js @@ -4,8 +4,8 @@ import { useHistory, useParams } from 'react-router'; import CSVReader from 'react-csv-reader'; import useAsyncEffect from 'use-async-effect'; -import getTotalRecords from '../../../util/instance/getTotalRecords'; -import csvDataLoad from '../../../util/instance/csvDataLoad'; +import getTotalRecords from '../../../api/instance/getTotalRecords'; +import csvDataLoad from '../../../api/instance/csvDataLoad'; import commaNumbers from '../../../util/format/commaNumbers'; import Worker from '../../../util/processCSV.worker'; diff --git a/src/components/instance/browse/datatable.js b/src/components/instance/browse/datatable.js index 57624a927..c1b1e182c 100644 --- a/src/components/instance/browse/datatable.js +++ b/src/components/instance/browse/datatable.js @@ -6,7 +6,7 @@ import useInterval from 'use-interval'; import { Card, CardBody, Col, Row } from '@nio/ui-kit'; import commaNumbers from '../../../util/format/commaNumbers'; -import getTableData from '../../../util/instance/getTableData'; +import getTableData from '../../../api/instance/getTableData'; import defaultTableState from '../../../util/state/defaultTableState'; const dataRefreshInterval = 3000; diff --git a/src/components/instance/browse/index.js b/src/components/instance/browse/index.js index b31a93a72..ece29dca0 100644 --- a/src/components/instance/browse/index.js +++ b/src/components/instance/browse/index.js @@ -8,7 +8,7 @@ import DataTable from './datatable'; import EntityManager from '../../shared/entityManager'; import JSONViewer from './jsonviewer'; import CSVUploader from './csvuploader'; -import setStructureEntities from '../../../util/instance/setStructureEntities'; +import buildInstanceStructure from '../../../util/buildInstanceStructure'; import handleSchemaTableRedirect from '../../../util/handleSchemaTableRedirect'; export default ({ auth, structure, refreshInstance }) => { @@ -21,7 +21,7 @@ export default ({ auth, structure, refreshInstance }) => { }, [schema, table, entities]); useAsyncEffect(() => { - if (structure) setEntities(setStructureEntities({ structure, schema, table })); + if (structure) setEntities(buildInstanceStructure({ structure, schema, table })); }, [structure, schema, table]); return structure ? ( diff --git a/src/components/instance/browse/jsonviewer.js b/src/components/instance/browse/jsonviewer.js index f88065de9..d5209501b 100644 --- a/src/components/instance/browse/jsonviewer.js +++ b/src/components/instance/browse/jsonviewer.js @@ -5,7 +5,7 @@ import { Button, Card, CardBody, Col, Form, Row } from '@nio/ui-kit'; import { useHistory, useParams } from 'react-router'; import useAsyncEffect from 'use-async-effect'; -import queryInstance from '../../../util/instance/_queryInstance'; +import queryInstance from '../../../api/queryInstance'; export default ({ newEntityColumns, hashAttribute, instance_id, refreshInstance, auth }) => { const history = useHistory(); diff --git a/src/components/instance/clustering/manage.js b/src/components/instance/clustering/manage.js index f1cdd7e1e..1cb612ca5 100644 --- a/src/components/instance/clustering/manage.js +++ b/src/components/instance/clustering/manage.js @@ -7,12 +7,13 @@ import useAsyncEffect from 'use-async-effect'; import EntityManager from '../../shared/entityManager'; import useLMS from '../../../stores/lmsData'; import DataTable from './datatable'; -import setStructureEntities from '../../../util/instance/setStructureEntities'; +import buildInstanceStructure from '../../../util/buildInstanceStructure'; import handleSchemaTableRedirect from '../../../util/handleSchemaTableRedirect'; +import defaultLMSData from '../../../util/state/defaultLMSData'; export default ({ auth, network, refreshInstance, structure }) => { const history = useHistory(); - const [lmsData] = useLMS({ auth: false, instances: [] }); + const [lmsData] = useLMS(defaultLMSData); const { instance_id, schema, table } = useParams(); const [entities, setEntities] = useState({ schemas: [], tables: [] }); @@ -21,7 +22,7 @@ export default ({ auth, network, refreshInstance, structure }) => { }, [schema, table, entities]); useAsyncEffect(() => { - if (structure) setEntities(setStructureEntities({ structure, schema, table })); + if (structure) setEntities(buildInstanceStructure({ structure, schema, table })); }, [structure, schema, table]); return ( diff --git a/src/components/instance/clustering/setup_enable.js b/src/components/instance/clustering/setup_enable.js index bd703da1a..e645299ca 100644 --- a/src/components/instance/clustering/setup_enable.js +++ b/src/components/instance/clustering/setup_enable.js @@ -2,14 +2,14 @@ import React, { useState } from 'react'; import { Row, Col, Button } from '@nio/ui-kit'; import useAsyncEffect from 'use-async-effect'; -import enableInstanceClustering from '../../../util/instance/enableInstanceClustering'; +import configureCluster from '../../../api/instance/configureCluster'; export default ({ port, username, instanceId, auth, refreshInstance }) => { const [submitted, setSubmitted] = useState(false); useAsyncEffect(async () => { if (submitted) { - const result = await enableInstanceClustering({ port, username, instanceId, auth, refreshInstance }); + const result = await configureCluster({ port, username, instanceId, auth, refreshInstance }); console.log(result); } }, [submitted]); diff --git a/src/components/instance/clustering/setup_role.js b/src/components/instance/clustering/setup_role.js index 5e39d9bb8..966c6e4aa 100644 --- a/src/components/instance/clustering/setup_role.js +++ b/src/components/instance/clustering/setup_role.js @@ -1,7 +1,7 @@ import React from 'react'; import { Row, Col, Button } from '@nio/ui-kit'; -import createClusterUserRole from '../../../util/instance/createClusterUserRole'; +import createClusterUserRole from '../../../api/instance/createClusterUserRole'; export default ({ auth, clusterRole, refreshInstance }) => (clusterRole ? ( diff --git a/src/components/instance/clustering/setup_user.js b/src/components/instance/clustering/setup_user.js index 93283ea9b..53e51b9bf 100644 --- a/src/components/instance/clustering/setup_user.js +++ b/src/components/instance/clustering/setup_user.js @@ -4,7 +4,7 @@ import useAsyncEffect from 'use-async-effect'; import { useAlert } from 'react-alert'; import defaultFormData from '../../../util/state/defaultClusterFormData'; -import createClusterUser from '../../../util/instance/createClusterUser'; +import createClusterUser from '../../../api/instance/createClusterUser'; export default ({ clusterUser, clusterRole, auth, refreshInstance }) => { const alert = useAlert(); diff --git a/src/components/instance/config/index.js b/src/components/instance/config/index.js new file mode 100644 index 000000000..89bcd49bc --- /dev/null +++ b/src/components/instance/config/index.js @@ -0,0 +1,22 @@ +import React from 'react'; +import { Row, Col } from '@nio/ui-kit'; + +import UpdateInstanceForm from './updateInstanceForm'; +import InstanceDetails from './instanceDetails'; + +export default ({ auth, details, refreshInstance }) => ( + + + + + + + + +); diff --git a/src/components/instance/config/instanceDetails.js b/src/components/instance/config/instanceDetails.js new file mode 100644 index 000000000..37e0f4887 --- /dev/null +++ b/src/components/instance/config/instanceDetails.js @@ -0,0 +1,115 @@ +import React from 'react'; +import { Card, CardBody, Row, Col } from '@nio/ui-kit'; + +export default ({ details }) => ( + <> + instance details + + + { details ? ( + <> + + + HarperDB Cloud® + + + {details.is_local ? 'no' : 'yes'} + + +
+ + + Product ID + + + {details.stripe_product_id} + + +
+ + + Instance Status + + + {details.status} + + +
+ + + Instance Name + + + {details.instance_name} + + +
+ + + Instance URL + + + {`http${details.domain_name || details.is_ssl ? 's' : ''}://${details.domain_name || details.host}:${details.port}`} + + + {details.instance_region && ( + <> +
+ + + AWS Region + + + {details.instance_region} + + + + )} +
+ + + RAM Allotment + + + {details.instance_ram}GB + + +
+ + + Disk Space + + + {details.instance_disk_space_gigs}GB + + +
+ + + Cost + + + ${details.is_local ? `${details.local_price_annual}/year` : `${details.cloud_price_monthly}/month`} + + + {details.local_price_annual ? ( + <> +
+ + + Next Charge Date + + + {details.exp_date ? new Date(details.exp_date).toLocaleDateString() : ''} + + + + ) : null} + + ) : ( + + )} +
+
+ +); diff --git a/src/components/instance/config/updateInstanceForm.js b/src/components/instance/config/updateInstanceForm.js new file mode 100644 index 000000000..2536f0b2a --- /dev/null +++ b/src/components/instance/config/updateInstanceForm.js @@ -0,0 +1,84 @@ +import React, { useState } from 'react'; +import { Button, Card, CardBody, Input, RadioCheckbox } from '@nio/ui-kit'; +import useAsyncEffect from 'use-async-effect'; + +import useLMS from '../../../stores/lmsData'; +import updateInstance from '../../../api/lms/updateInstance'; +import updateLicense from '../../../api/lms/updateLicense'; +import setLicense from '../../../api/instance/setLicense'; +import defaultLMSData from '../../../util/state/defaultLMSData'; +import defaultInstanceFormData from '../../../util/state/defaultInstanceFormData'; + +export default ({ instanceAuth, details, refreshInstance }) => { + const [{ auth, products }] = useLMS(defaultLMSData); + const [formData, updateForm] = useState(defaultInstanceFormData); + const instanceProducts = !products ? false : details.is_local ? products.local : products.cloud; + const defaultValue = instanceProducts && instanceProducts.find((p) => p.value === details.stripe_product_id); + + useAsyncEffect(async () => { + if (formData.submitted) { + const { stripe_product_id, instance_name } = formData; + const { instance_id, customer_id, license_id, fingerprint } = details; + + const newLicense = await updateLicense({ auth, payload: { license_id, stripe_product_id, instance_id, customer_id, fingerprint } }); + const licenseApplied = await setLicense({ auth: instanceAuth, key: newLicense.key, company: newLicense.company }); + const newInstance = await updateInstance({ auth, payload: { stripe_product_id, instance_id, customer_id, instance_name } }); + console.log(newLicense, licenseApplied, newInstance); + updateForm({ ...formData, submitted: false }); + refreshInstance(Date.now()); + } + }, [formData]); + + useAsyncEffect(() => { + if (details) { + updateForm({ ...formData, stripe_product_id: details.stripe_product_id, instance_name: details.instance_name }); + } + }, [details]); + + return ( + <> + resize instance + + + {products && defaultValue ? ( + <> +
Instance Name
+
+ updateForm({ ...formData, instance_name: e.target.value, error: false })} + type="text" + title="instance_name" + value={formData.instance_name} + /> +
+ +
Instance Type
+
+ updateForm({ ...formData, stripe_product_id: value })} + options={instanceProducts} + value={formData.stripe_product_id || ''} + defaultValue={defaultValue} + /> +
+ + + + ) : ( + + )} +
+
+ + ); +}; diff --git a/src/components/instance/index.js b/src/components/instance/index.js index 1bb37f259..bb5de6fc7 100644 --- a/src/components/instance/index.js +++ b/src/components/instance/index.js @@ -6,18 +6,27 @@ import SubNav from './subnav'; import routes from '../../routes/instance'; import useLMS from '../../stores/lmsData'; import useInstanceAuth from '../../stores/instanceAuths'; -import buildActiveInstanceObject from '../../util/instance/buildActiveInstanceObject'; +import buildActiveInstanceObject from '../../util/buildActiveInstanceObject'; +import defaultLMSData from '../../util/state/defaultLMSData'; +import defaultActiveInstance from '../../util/state/defaultActiveInstance'; +import getInstances from '../../api/lms/getInstances'; +import getLicenses from '../../api/lms/getLicenses'; +import getProducts from '../../api/lms/getProducts'; export default () => { const { instance_id } = useParams(); - const [lmsData] = useLMS({ auth: false, instances: [] }); + const [lmsData, setLMSData] = useLMS(defaultLMSData); const [instanceAuths] = useInstanceAuth({}); - const [activeInstance, setActiveInstance] = useState({ auth: false, structure: false, network: false }); - const [lastUpdate, refreshInstance] = useState({ auth: false, structure: false, network: false }); + const [activeInstance, setActiveInstance] = useState(defaultActiveInstance); + const [lastUpdate, refreshInstance] = useState(false); useAsyncEffect(async () => { if (instance_id) { - const activeInstanceObject = await buildActiveInstanceObject({ instance_id, instanceAuths, lmsData }); + const instances = await getInstances({ auth: lmsData.auth }); + const licenses = await getLicenses({ auth: lmsData.auth }); + const products = await getProducts({ auth: lmsData.auth }); + setLMSData({ ...lmsData, instances, licenses, products }); + const activeInstanceObject = await buildActiveInstanceObject({ instance_id, instanceAuths, lmsData: { ...lmsData, instances, licenses, products } }); if (!activeInstanceObject.error) { setActiveInstance(activeInstanceObject); } diff --git a/src/components/instance/license/index.js b/src/components/instance/license/index.js deleted file mode 100644 index 0bb5b6f23..000000000 --- a/src/components/instance/license/index.js +++ /dev/null @@ -1,15 +0,0 @@ -import React from 'react'; -import { Card, CardBody, Row, Col } from '@nio/ui-kit'; - -export default () => ( - - - license - - - content here - - - - -); diff --git a/src/components/instances/cardBack.js b/src/components/instances/cardBack.js index df08aaefd..d14af01a1 100644 --- a/src/components/instances/cardBack.js +++ b/src/components/instances/cardBack.js @@ -2,7 +2,7 @@ import React, { useState } from 'react'; import { Button, Card, CardBody, Input, Row, Col } from '@nio/ui-kit'; import useAsyncEffect from 'use-async-effect'; import { useAlert } from 'react-alert'; -import queryInstance from '../../util/instance/_queryInstance'; +import queryInstance from '../../api/queryInstance'; import defaultFormData from '../../util/state/defaultAuthFormData'; export default ({ id, host, port, is_ssl, setAuth, flipCard, flipState }) => { diff --git a/src/components/instances/cloudInstanceForm.js b/src/components/instances/cloudInstanceForm.js index 0df61924b..0d9d96046 100644 --- a/src/components/instances/cloudInstanceForm.js +++ b/src/components/instances/cloudInstanceForm.js @@ -86,6 +86,18 @@ export default ({ products, regions, setInstanceDetails, needsCard }) => { defaultValue={regions[0]} />
+ +
Instance Cost
+
+ updateForm({ ...formData, stripe_product_id: value })} + options={products} + value={formData.stripe_product_id || ''} + defaultValue={products[0]} + /> +
), + ? updateNode({ channel, subscriptions, buttonState: 'togglePublish', id, host, clusterPort, auth, refreshInstance })} /> + : ), width: 80, style: toggleCellPadding, }, { @@ -30,7 +30,7 @@ export default ({ auth, refreshInstance }) => [{ original: { id, host, clusterPort, subscribe, connection, channel, subscriptions }, }) => ( connection - ? updateInstanceSubscription({ channel, subscriptions, buttonState: 'toggleSubscribe', id, host, clusterPort, auth, refreshInstance })} /> + ? updateNode({ channel, subscriptions, buttonState: 'toggleSubscribe', id, host, clusterPort, auth, refreshInstance })} /> : '' ), width: 80, diff --git a/src/util/datatable/customerUserColumns.js b/src/util/datatable/customerUserColumns.js new file mode 100644 index 000000000..276ef8295 --- /dev/null +++ b/src/util/datatable/customerUserColumns.js @@ -0,0 +1,28 @@ +import React from 'react'; +import { Button } from '@nio/ui-kit'; + +import removeUser from '../../api/lms/removeUser'; + +const toggleCellPadding = { paddingTop: 3, paddingBottom: 0, paddingLeft: 0, paddingRight: 2 }; + +export default ({ auth, customer_id, setLastUpdate }) => [{ + Header: 'last name', + accessor: 'lastname', + style: { paddingTop: 10 }, +}, { + Header: 'first name', + accessor: 'firstname', + style: { paddingTop: 10 }, +}, { + Header: 'email', + accessor: 'email', + style: { paddingTop: 10 }, +}, { + Header: '', + Cell: ({ + original: { user_id }, + }) => ( + ), + width: 80, + style: toggleCellPadding, +}]; diff --git a/src/util/instance/buildActiveInstanceObject.js b/src/util/instance/buildActiveInstanceObject.js deleted file mode 100644 index 2c6b47013..000000000 --- a/src/util/instance/buildActiveInstanceObject.js +++ /dev/null @@ -1,15 +0,0 @@ -import queryInstance from './_queryInstance'; -import getInstanceDataStructure from './getInstanceDataStructure'; -import getInstanceClusterStatus from './getInstanceClusterStatus'; - -export default async ({ instance_id, instanceAuths, lmsData }) => { - const auth = { ...instanceAuths[instance_id], ...lmsData.instances.find((i) => i.id === instance_id) }; - const instance = await queryInstance({ operation: 'describe_all' }, auth); - - if (instance.error) { - return instance; - } - const structure = getInstanceDataStructure(instance); - const network = await getInstanceClusterStatus(auth); - return { auth, structure, network }; -}; diff --git a/src/util/isEmail.js b/src/util/isEmail.js new file mode 100644 index 000000000..174e2e9ad --- /dev/null +++ b/src/util/isEmail.js @@ -0,0 +1,4 @@ +export default (email) => { + const re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; + return re.test(String(email).toLowerCase()); +}; diff --git a/src/util/lms/_queryLMS.js b/src/util/lms/_queryLMS.js deleted file mode 100644 index d0e136343..000000000 --- a/src/util/lms/_queryLMS.js +++ /dev/null @@ -1,49 +0,0 @@ -import instances from '../../../mock_data/LMS_API.instances.json'; -import products from '../../../mock_data/LMS_API.products.json'; -import regions from '../../../mock_data/LMS_API.aws_regions.json'; -import customer from '../../../mock_data/LMS_API.customer.json'; -import user from '../../../mock_data/LMS_API.user.json'; -import invoices from '../../../mock_data/LMS_API.invoices.json'; - -// eslint-disable-next-line no-unused-vars -export default async ({ endpoint, payload, auth }) => { - /* - const request = await fetch( - `https://api.harperdb.io/v1/${endpoint}`, - { - method: 'POST', - body: JSON.stringify(payload), - headers: { - 'Content-Type': 'application/json', - authorization: `Basic ${btoa(`${auth.user}:${auth.pass}`)}`, - }, - }, - ); - return request.json(); - */ - - console.log('Calling API', endpoint); - - switch (endpoint) { - case 'getUser': - return user; - case 'getCustomer': - return customer; - case 'getInstances': - return instances; - case 'getProducts': - return products; - case 'getRegions': - return regions; - case 'getCustomerInvoices': - return invoices; - case 'addPaymentMethod': - return { result: true, message: 'Payment method added to account' }; - case 'removePaymentMethod': - return { result: true, message: 'Payment method removed from account.' }; - case 'addInstance': - return { result: true, message: 'Instance ', instance_id: 'guid' }; - default: - return { error: 'unknown endpoint' }; - } -}; diff --git a/src/util/lms/getProductsAndRegions.js b/src/util/lms/getProductsAndRegions.js deleted file mode 100644 index f6251e283..000000000 --- a/src/util/lms/getProductsAndRegions.js +++ /dev/null @@ -1,20 +0,0 @@ -import queryLMS from './_queryLMS'; - -export default async ({ auth }) => { - const newProducts = await queryLMS({ - endpoint: 'getProducts', - method: 'POST', - auth, - }); - const newRegions = await queryLMS({ - endpoint: 'getRegions', - method: 'POST', - auth, - }); - return { - raw: newProducts, - local: newProducts.map((p) => ({ label: `${p.instance_ram}GB RAM | ${p.instance_disk_space_gigs}GB Disk Space | $${p.local_price_annual}/year`, value: p.stripe_product_id })), - cloud: newProducts.map((p) => ({ label: `${p.instance_ram}GB RAM | ${p.instance_disk_space_gigs}GB Disk Space | $${p.cloud_price_monthly}/month`, value: p.stripe_product_id })), - regions: newRegions, - }; -}; diff --git a/src/util/state/defaultActiveInstance.js b/src/util/state/defaultActiveInstance.js new file mode 100644 index 000000000..f697b97a1 --- /dev/null +++ b/src/util/state/defaultActiveInstance.js @@ -0,0 +1,8 @@ +export default { + auth: false, + structure: false, + network: false, + product: false, + license: false, + details: false, +}; diff --git a/src/util/state/defaultAuthFormData.js b/src/util/state/defaultAuthFormData.js index 8e1ebe214..e298c97c2 100644 --- a/src/util/state/defaultAuthFormData.js +++ b/src/util/state/defaultAuthFormData.js @@ -1 +1,6 @@ -export default { submitted: false, error: false, user: false, pass: false }; +export default { + submitted: false, + error: false, + user: false, + pass: false, +}; diff --git a/src/util/state/defaultUserFormData.js b/src/util/state/defaultUserFormData.js new file mode 100644 index 000000000..848ff4aff --- /dev/null +++ b/src/util/state/defaultUserFormData.js @@ -0,0 +1,7 @@ +export default { + submitted: false, + error: false, + firstname: '', + lastname: '', + email: '', +}; diff --git a/src/util/instance/getInstancePrice.js b/src/util/stripe/getInstancePrice.js similarity index 100% rename from src/util/instance/getInstancePrice.js rename to src/util/stripe/getInstancePrice.js From 4b862e80ee9864b1ab40b61ca83b314bcf4b920a Mon Sep 17 00:00:00 2001 From: deliciousmonster Date: Wed, 26 Feb 2020 11:23:24 -0700 Subject: [PATCH 14/19] re-organize, styles, loaders, animations, polish --- README.md | 10 ++-- src/assets/styles/_base.scss | 19 +++++-- src/assets/styles/_card.scss | 2 +- src/assets/styles/_forms.scss | 2 +- src/assets/styles/_modal.scss | 13 ++--- src/assets/styles/_react-table.scss | 32 +++++++++-- .../cardEdit.js} | 10 ++-- .../cardStatic.js} | 2 +- .../account/{billing.js => billing/index.js} | 12 ++--- .../invoices.js} | 6 +-- src/components/account/index.js | 4 +- .../account/{profile.js => profile/index.js} | 4 +- .../password.js} | 2 +- .../profile.js} | 2 +- .../account/routes.js} | 6 +-- src/components/account/subnav.js | 19 ------- .../account/{users_add.js => users/add.js} | 6 +-- .../datatable.js} | 2 +- .../account/{users.js => users/index.js} | 12 ++--- src/components/instance/browse/csvuploader.js | 2 +- src/components/instance/browse/datatable.js | 6 +-- src/components/instance/browse/index.js | 54 ++++++++++--------- .../instance/clustering/datatable.js | 2 +- src/components/instance/clustering/manage.js | 4 +- .../instance/clustering/setup_port.js | 2 +- .../instance/clustering/setup_user.js | 2 +- .../instance/config/updateInstanceForm.js | 6 +-- src/components/instance/index.js | 12 ++--- src/components/instance/routes.js | 13 +++++ src/components/instance/subnav.js | 15 ------ src/components/instances/index.js | 36 ++++++------- .../instances/{ => list}/cardBack.js | 4 +- .../instances/{ => list}/cardFront.js | 0 .../instances/{ => list}/instanceCard.js | 0 .../instances/{ => list}/newInstanceCard.js | 2 +- .../{cloudInstanceForm.js => new/cloud.js} | 13 +---- .../{confirmOrderForm.js => new/confirm.js} | 0 .../{localInstanceForm.js => new/local.js} | 2 +- .../{newInstanceModal.js => new/modal.js} | 27 +++++----- .../payment.js} | 2 +- .../{instanceTypeForm.js => new/type.js} | 0 src/components/login.js | 6 +-- src/components/shared/subnav.js | 24 +++++++++ src/components/topnav.js | 4 +- src/routes/instance.js | 13 ----- .../defaults}/defaultActiveInstance.js | 0 .../defaults}/defaultAuthFormData.js | 0 .../defaults}/defaultClusterFormData.js | 0 .../defaults}/defaultInstanceFormData.js | 0 .../defaults}/defaultLMSData.js | 0 .../defaults}/defaultProfileFormData.js | 0 .../defaults}/defaultTableState.js | 0 .../defaults}/defaultUserFormData.js | 0 src/{stores => state}/instanceAuths.js | 0 src/{stores => state}/lmsData.js | 0 src/util/{format => }/commaNumbers.js | 0 56 files changed, 217 insertions(+), 199 deletions(-) rename src/components/account/{billing_cardEdit.js => billing/cardEdit.js} (93%) rename src/components/account/{billing_cardStatic.js => billing/cardStatic.js} (97%) rename src/components/account/{billing.js => billing/index.js} (79%) rename src/components/account/{billing_invoices.js => billing/invoices.js} (82%) rename src/components/account/{profile.js => profile/index.js} (85%) rename src/components/account/{profile_password.js => profile/password.js} (96%) rename src/components/account/{profile_profile.js => profile/profile.js} (96%) rename src/{routes/account.js => components/account/routes.js} (64%) delete mode 100644 src/components/account/subnav.js rename src/components/account/{users_add.js => users/add.js} (93%) rename src/components/account/{users_datatable.js => users/datatable.js} (95%) rename src/components/account/{users.js => users/index.js} (73%) create mode 100644 src/components/instance/routes.js delete mode 100644 src/components/instance/subnav.js rename src/components/instances/{ => list}/cardBack.js (94%) rename src/components/instances/{ => list}/cardFront.js (100%) rename src/components/instances/{ => list}/instanceCard.js (100%) rename src/components/instances/{ => list}/newInstanceCard.js (92%) rename src/components/instances/{cloudInstanceForm.js => new/cloud.js} (86%) rename src/components/instances/{confirmOrderForm.js => new/confirm.js} (100%) rename src/components/instances/{localInstanceForm.js => new/local.js} (98%) rename src/components/instances/{newInstanceModal.js => new/modal.js} (82%) rename src/components/instances/{customerPaymentForm.js => new/payment.js} (97%) rename src/components/instances/{instanceTypeForm.js => new/type.js} (100%) create mode 100644 src/components/shared/subnav.js delete mode 100644 src/routes/instance.js rename src/{util/state => state/defaults}/defaultActiveInstance.js (100%) rename src/{util/state => state/defaults}/defaultAuthFormData.js (100%) rename src/{util/state => state/defaults}/defaultClusterFormData.js (100%) rename src/{util/state => state/defaults}/defaultInstanceFormData.js (100%) rename src/{util/state => state/defaults}/defaultLMSData.js (100%) rename src/{util/state => state/defaults}/defaultProfileFormData.js (100%) rename src/{util/state => state/defaults}/defaultTableState.js (100%) rename src/{util/state => state/defaults}/defaultUserFormData.js (100%) rename src/{stores => state}/instanceAuths.js (100%) rename src/{stores => state}/lmsData.js (100%) rename src/util/{format => }/commaNumbers.js (100%) diff --git a/README.md b/README.md index 062324235..80146f772 100644 --- a/README.md +++ b/README.md @@ -18,19 +18,17 @@ Follow these steps to create a simple UI to interact with your HarperDB instance 1. In your terminal, clone the UI scaffold, enter the directory, and install dependencies. ``` - git clone https://github.com/harperdb/ui-scaffold.git my-project - cd my-project + git clone https://github.com/harperdb/hdbms.git + cd hdbms npm i -s ``` -1. In the root of the project, rename `config.js.example` to `config.js`, and fill out the details for your HarperDB instance. - 1. Start the project. ``` npm start ``` -1. Visit the project at https://0.0.0.0:3000. +1. Visit the project at http://localhost:3000. - The development web server uses a self-signed certificate, and you may see a warning about the site being insecure. In your local development environment, it is safe to click "Advanced" > "proceed to site anyway." -You’ll see a simple UI with the result of a `describe_all` operation. +1. Log into the mock instances with admin/Abc1234! diff --git a/src/assets/styles/_base.scss b/src/assets/styles/_base.scss index 83ba18c87..0272562b1 100644 --- a/src/assets/styles/_base.scss +++ b/src/assets/styles/_base.scss @@ -36,11 +36,12 @@ body { #app-nav { min-width: $minWidth; - background: $black-overlay !important; + background: $gradient-purple-magenta !important; padding: 0 2rem; position: fixed; z-index: 99 !important; height: 60px; + box-shadow: 0 0 5px $dark-grey-overlay; @media screen and (max-width: 991px) { padding: 0 1rem; @@ -62,13 +63,19 @@ body { #app-subnav { width: 100%; min-width: $minWidth; - background: $dark-grey-overlay !important; + background: $grey-overlay !important; + box-shadow: 0 0 5px $dark-grey-overlay; padding: 0 2rem; position: fixed; z-index: 98 !important; height: 40px; - top: 60px !important; + top: 20px !important; text-align: center; + transition: top 250ms; + + &.show { + top: 60px !important; + } .navbar-nav { width: 100%; @@ -79,6 +86,12 @@ body { .nav-item { padding: 0 10px; + @media screen and (max-width: 991px) { + padding: 0 5px; + font-size: 0.875rem; + } + + &:first-child { padding-left: 0; } diff --git a/src/assets/styles/_card.scss b/src/assets/styles/_card.scss index a8be73280..4ed83bc38 100644 --- a/src/assets/styles/_card.scss +++ b/src/assets/styles/_card.scss @@ -2,7 +2,7 @@ background: $light-white-overlay !important; color: $color-black; border-radius: 4px !important; - box-shadow: 0 0 10px $grey-overlay; + box-shadow: 0 0 5px $dark-grey-overlay; .card-body { padding: 15px; diff --git a/src/assets/styles/_forms.scss b/src/assets/styles/_forms.scss index 949621194..befd9124a 100644 --- a/src/assets/styles/_forms.scss +++ b/src/assets/styles/_forms.scss @@ -18,7 +18,7 @@ input:-webkit-autofill:active { box-shadow: none !important; -webkit-text-fill-color: $color-black !important; -webkit-box-shadow: 0 0 0 1000px $light-white-overlay inset; - transition: background-color 5000s ease-in-out 0s; + transition: none; &::placeholder { text-align: left !important; diff --git a/src/assets/styles/_modal.scss b/src/assets/styles/_modal.scss index 687db53fc..7346fb9e9 100644 --- a/src/assets/styles/_modal.scss +++ b/src/assets/styles/_modal.scss @@ -1,13 +1,14 @@ #new-instance-modal { - .instance-form-card-holder { - .card { - box-shadow: none !important; + .card { + box-shadow: none !important; - .instance-form-card-body { - background: $light-grey-overlay; - } + .instance-form-card-body { + background: $light-grey-overlay; } + } + + .instance-form-card-holder { &:first-child{ @media screen and (max-width: 991px) { diff --git a/src/assets/styles/_react-table.scss b/src/assets/styles/_react-table.scss index c446c7c48..6c834b774 100755 --- a/src/assets/styles/_react-table.scss +++ b/src/assets/styles/_react-table.scss @@ -27,6 +27,7 @@ .rt-td, .rt-th { text-align: left !important; + border-right: none !important; &.-sort-desc { box-shadow: inset 0 -2px 0 0 $color-black; @@ -55,6 +56,9 @@ color: $color-white !important; font-family: $font-family-sans; font-weight: $font-weight-light; + min-width: 100px; + height: 33px; + margin-top: 3px; &:hover { background: $color-purple !important; @@ -62,9 +66,31 @@ } } - .-pageInfo, - .-pageSizeOptions { - margin: 0 10px; + .-previous, + .-next { + flex: 0 1; + } + + .-center { + flex: none; + } + + .-previous, + .-center, + .-next { + @media screen and (max-width: 905px) { + display: block; + width: 100%; + flex: none; + margin-bottom: 5px; + + .-pageInfo { + margin-left: 0; + } + .-pageSizeOptions { + margin-right: 0; + } + } } } diff --git a/src/components/account/billing_cardEdit.js b/src/components/account/billing/cardEdit.js similarity index 93% rename from src/components/account/billing_cardEdit.js rename to src/components/account/billing/cardEdit.js index e1a2b3c8c..dbc5af6ac 100644 --- a/src/components/account/billing_cardEdit.js +++ b/src/components/account/billing/cardEdit.js @@ -3,11 +3,11 @@ import { Button, Row, Col, Input } from '@nio/ui-kit'; import { CardNumberElement, CardExpiryElement, CardCvcElement, useElements, useStripe } from '@stripe/react-stripe-js'; import useAsyncEffect from 'use-async-effect'; -import cardOptions from '../../util/stripe/cardOptions'; -import useLMS from '../../stores/lmsData'; -import defaultLMSData from '../../util/state/defaultLMSData'; -import addPaymentMethod from '../../api/lms/addPaymentMethod'; -import getCustomer from '../../api/lms/getCustomer'; +import cardOptions from '../../../util/stripe/cardOptions'; +import useLMS from '../../../state/lmsData'; +import defaultLMSData from '../../../state/defaults/defaultLMSData'; +import addPaymentMethod from '../../../api/lms/addPaymentMethod'; +import getCustomer from '../../../api/lms/getCustomer'; export default ({ setEditingCard, customerCard }) => { const [lmsData, setLMSData] = useLMS(defaultLMSData); diff --git a/src/components/account/billing_cardStatic.js b/src/components/account/billing/cardStatic.js similarity index 97% rename from src/components/account/billing_cardStatic.js rename to src/components/account/billing/cardStatic.js index e9c645f21..4373cefea 100644 --- a/src/components/account/billing_cardStatic.js +++ b/src/components/account/billing/cardStatic.js @@ -3,7 +3,7 @@ import useAsyncEffect from 'use-async-effect'; import { Row, Col, Button } from '@nio/ui-kit'; import { useAlert } from 'react-alert'; -import removePaymentMethod from '../../api/lms/removePaymentMethod'; +import removePaymentMethod from '../../../api/lms/removePaymentMethod'; export default ({ customerId, customerCard, customerStripeId, setEditingCard, auth }) => { const [removing, setRemoving] = useState(false); diff --git a/src/components/account/billing.js b/src/components/account/billing/index.js similarity index 79% rename from src/components/account/billing.js rename to src/components/account/billing/index.js index 0474d9a9b..67d08d50b 100644 --- a/src/components/account/billing.js +++ b/src/components/account/billing/index.js @@ -1,12 +1,12 @@ import React, { useState } from 'react'; import { Row, Col, Card, CardBody } from '@nio/ui-kit'; -import customerHasChargeableCard from '../../util/stripe/customerHasChargeableCard'; -import useLMS from '../../stores/lmsData'; -import defaultLMSData from '../../util/state/defaultLMSData'; -import StaticCard from './billing_cardStatic'; -import EditCard from './billing_cardEdit'; -import Invoices from './billing_invoices'; +import customerHasChargeableCard from '../../../util/stripe/customerHasChargeableCard'; +import useLMS from '../../../state/lmsData'; +import defaultLMSData from '../../../state/defaults/defaultLMSData'; +import StaticCard from './cardStatic'; +import EditCard from './cardEdit'; +import Invoices from './invoices'; export default () => { const [lmsData] = useLMS(defaultLMSData); diff --git a/src/components/account/billing_invoices.js b/src/components/account/billing/invoices.js similarity index 82% rename from src/components/account/billing_invoices.js rename to src/components/account/billing/invoices.js index f21408335..d5a2cd94e 100644 --- a/src/components/account/billing_invoices.js +++ b/src/components/account/billing/invoices.js @@ -2,9 +2,9 @@ import React, { useState } from 'react'; import { Row, Col } from '@nio/ui-kit'; import useAsyncEffect from 'use-async-effect'; -import getInvoices from '../../api/lms/getInvoices'; -import useLMS from '../../stores/lmsData'; -import defaultLMSData from '../../util/state/defaultLMSData'; +import getInvoices from '../../../api/lms/getInvoices'; +import useLMS from '../../../state/lmsData'; +import defaultLMSData from '../../../state/defaults/defaultLMSData'; export default () => { const [lmsData] = useLMS(defaultLMSData); diff --git a/src/components/account/index.js b/src/components/account/index.js index ea2e153d9..3f3266753 100644 --- a/src/components/account/index.js +++ b/src/components/account/index.js @@ -1,8 +1,8 @@ import React from 'react'; import { Redirect, Route, Switch } from 'react-router-dom'; -import routes from '../../routes/account'; -import SubNav from './subnav'; +import routes from './routes'; +import SubNav from '../shared/subnav'; export default () => ( <> diff --git a/src/components/account/profile.js b/src/components/account/profile/index.js similarity index 85% rename from src/components/account/profile.js rename to src/components/account/profile/index.js index 851c308e6..6471b7c1e 100644 --- a/src/components/account/profile.js +++ b/src/components/account/profile/index.js @@ -1,8 +1,8 @@ import React from 'react'; import { Row, Col, Card, CardBody } from '@nio/ui-kit'; -import ProfileForm from './profile_profile'; -import PasswordForm from './profile_password'; +import ProfileForm from './profile'; +import PasswordForm from './password'; export default () => ( diff --git a/src/components/account/profile_password.js b/src/components/account/profile/password.js similarity index 96% rename from src/components/account/profile_password.js rename to src/components/account/profile/password.js index 2e98b67e7..dc31ae4b9 100644 --- a/src/components/account/profile_password.js +++ b/src/components/account/profile/password.js @@ -2,7 +2,7 @@ import React, { useState } from 'react'; import { Row, Col, Input, Button } from '@nio/ui-kit'; import useAsyncEffect from 'use-async-effect'; -import defaultProfileFormData from '../../util/state/defaultProfileFormData'; +import defaultProfileFormData from '../../../state/defaults/defaultProfileFormData'; export default () => { const [passwordForm, updatePasswordForm] = useState(defaultProfileFormData); diff --git a/src/components/account/profile_profile.js b/src/components/account/profile/profile.js similarity index 96% rename from src/components/account/profile_profile.js rename to src/components/account/profile/profile.js index 6c478b807..7ba0653c3 100644 --- a/src/components/account/profile_profile.js +++ b/src/components/account/profile/profile.js @@ -2,7 +2,7 @@ import React, { useState } from 'react'; import { Row, Col, Input, Button } from '@nio/ui-kit'; import useAsyncEffect from 'use-async-effect'; -import defaultProfileFormData from '../../util/state/defaultProfileFormData'; +import defaultProfileFormData from '../../../state/defaults/defaultProfileFormData'; export default () => { const [profileForm, updateProfileForm] = useState(defaultProfileFormData); diff --git a/src/routes/account.js b/src/components/account/routes.js similarity index 64% rename from src/routes/account.js rename to src/components/account/routes.js index a02d06965..8e0d6c33a 100644 --- a/src/routes/account.js +++ b/src/components/account/routes.js @@ -1,6 +1,6 @@ -import Profile from '../components/account/profile'; -import Users from '../components/account/users'; -import Billing from '../components/account/billing'; +import Profile from './profile'; +import Users from './users'; +import Billing from './billing'; export default [ { component: Profile, path: '/account/profile', link: 'profile', icon: 'user' }, diff --git a/src/components/account/subnav.js b/src/components/account/subnav.js deleted file mode 100644 index c0089d8d1..000000000 --- a/src/components/account/subnav.js +++ /dev/null @@ -1,19 +0,0 @@ -import React from 'react'; -import { Navbar, Nav, NavItem } from '@nio/ui-kit'; -import { NavLink } from 'react-router-dom'; - - -export default ({ routes = [] }) => ( - - - -); diff --git a/src/components/account/users_add.js b/src/components/account/users/add.js similarity index 93% rename from src/components/account/users_add.js rename to src/components/account/users/add.js index 667428d30..fd07ffdac 100644 --- a/src/components/account/users_add.js +++ b/src/components/account/users/add.js @@ -3,9 +3,9 @@ import { Input, Button, Card, CardBody } from '@nio/ui-kit'; import useAsyncEffect from 'use-async-effect'; import { useAlert } from 'react-alert'; -import addUser from '../../api/lms/addUser'; -import defaultUserFormData from '../../util/state/defaultUserFormData'; -import isEmail from '../../util/isEmail'; +import addUser from '../../../api/lms/addUser'; +import defaultUserFormData from '../../../state/defaults/defaultUserFormData'; +import isEmail from '../../../util/isEmail'; export default ({ setLastUpdate, customerId }) => { const alert = useAlert(); diff --git a/src/components/account/users_datatable.js b/src/components/account/users/datatable.js similarity index 95% rename from src/components/account/users_datatable.js rename to src/components/account/users/datatable.js index 2bb7e7e7e..bc201e2ff 100644 --- a/src/components/account/users_datatable.js +++ b/src/components/account/users/datatable.js @@ -2,7 +2,7 @@ import React, { useState } from 'react'; import { Card, CardBody, Row, Col } from '@nio/ui-kit'; import ReactTable from 'react-table'; -import defaultTableState from '../../util/state/defaultTableState'; +import defaultTableState from '../../../state/defaults/defaultTableState'; export default ({ tableData }) => { const [tableState, setTableState] = useState({ ...defaultTableState, sorted: [{ id: 'lastname', desc: false }] }); diff --git a/src/components/account/users.js b/src/components/account/users/index.js similarity index 73% rename from src/components/account/users.js rename to src/components/account/users/index.js index de6630f02..3cbbb70f4 100644 --- a/src/components/account/users.js +++ b/src/components/account/users/index.js @@ -2,12 +2,12 @@ import React, { useState } from 'react'; import { Row, Col } from '@nio/ui-kit'; import useAsyncEffect from 'use-async-effect'; -import DataTable from './users_datatable'; -import AddUserForm from './users_add'; -import getUsers from '../../api/lms/getUsers'; -import useLMS from '../../stores/lmsData'; -import defaultLMSData from '../../util/state/defaultLMSData'; -import customerUserColumns from '../../util/datatable/customerUserColumns'; +import DataTable from './datatable'; +import AddUserForm from './add'; +import getUsers from '../../../api/lms/getUsers'; +import useLMS from '../../../state/lmsData'; +import defaultLMSData from '../../../state/defaults/defaultLMSData'; +import customerUserColumns from '../../../util/datatable/customerUserColumns'; export default () => { const [{ auth, customer }] = useLMS(defaultLMSData); diff --git a/src/components/instance/browse/csvuploader.js b/src/components/instance/browse/csvuploader.js index b61d8a927..3dd0ecebe 100644 --- a/src/components/instance/browse/csvuploader.js +++ b/src/components/instance/browse/csvuploader.js @@ -6,7 +6,7 @@ import useAsyncEffect from 'use-async-effect'; import getTotalRecords from '../../../api/instance/getTotalRecords'; import csvDataLoad from '../../../api/instance/csvDataLoad'; -import commaNumbers from '../../../util/format/commaNumbers'; +import commaNumbers from '../../../util/commaNumbers'; import Worker from '../../../util/processCSV.worker'; const worker = new Worker(); diff --git a/src/components/instance/browse/datatable.js b/src/components/instance/browse/datatable.js index c1b1e182c..4a7a4c3a5 100644 --- a/src/components/instance/browse/datatable.js +++ b/src/components/instance/browse/datatable.js @@ -5,9 +5,9 @@ import useAsyncEffect from 'use-async-effect'; import useInterval from 'use-interval'; import { Card, CardBody, Col, Row } from '@nio/ui-kit'; -import commaNumbers from '../../../util/format/commaNumbers'; +import commaNumbers from '../../../util/commaNumbers'; import getTableData from '../../../api/instance/getTableData'; -import defaultTableState from '../../../util/state/defaultTableState'; +import defaultTableState from '../../../state/defaults/defaultTableState'; const dataRefreshInterval = 3000; let tableChangeTimeout = false; @@ -65,7 +65,7 @@ export default ({ activeTable: { hashAttribute, dataTableColumns }, auth, instan - + { if (structure) setEntities(buildInstanceStructure({ structure, schema, table })); }, [structure, schema, table]); - return structure ? ( + return ( - - { schema && ( - + { schema && table && !entities.activeTable ? ( + + ) : ( + <> + + { schema && ( + + )} + )} - { schema && table && action === 'csv' && entities.activeTable ? ( + { schema && table && !entities.activeTable ? ( + + ) : schema && table && action === 'csv' && entities.activeTable ? ( { )} - ) : ( - ); }; diff --git a/src/components/instance/clustering/datatable.js b/src/components/instance/clustering/datatable.js index a34ec8ca5..15b1846e5 100644 --- a/src/components/instance/clustering/datatable.js +++ b/src/components/instance/clustering/datatable.js @@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react'; import ReactTable from 'react-table'; import { Card, CardBody, Col, Row } from '@nio/ui-kit'; -import defaultTableState from '../../../util/state/defaultTableState'; +import defaultTableState from '../../../state/defaults/defaultTableState'; import clusterConfigColumns from '../../../util/datatable/clusterConfigColumns'; export default ({ instances, network, schema, table, auth, refreshInstance }) => { diff --git a/src/components/instance/clustering/manage.js b/src/components/instance/clustering/manage.js index 1cb612ca5..0667eba5b 100644 --- a/src/components/instance/clustering/manage.js +++ b/src/components/instance/clustering/manage.js @@ -5,11 +5,11 @@ import { useHistory } from 'react-router'; import useAsyncEffect from 'use-async-effect'; import EntityManager from '../../shared/entityManager'; -import useLMS from '../../../stores/lmsData'; +import useLMS from '../../../state/lmsData'; import DataTable from './datatable'; import buildInstanceStructure from '../../../util/buildInstanceStructure'; import handleSchemaTableRedirect from '../../../util/handleSchemaTableRedirect'; -import defaultLMSData from '../../../util/state/defaultLMSData'; +import defaultLMSData from '../../../state/defaults/defaultLMSData'; export default ({ auth, network, refreshInstance, structure }) => { const history = useHistory(); diff --git a/src/components/instance/clustering/setup_port.js b/src/components/instance/clustering/setup_port.js index 9f5dbbc75..6494d8d3b 100644 --- a/src/components/instance/clustering/setup_port.js +++ b/src/components/instance/clustering/setup_port.js @@ -3,7 +3,7 @@ import { Row, Col, Button, Input } from '@nio/ui-kit'; import useAsyncEffect from 'use-async-effect'; import { useAlert } from 'react-alert'; -import defaultFormData from '../../../util/state/defaultClusterFormData'; +import defaultFormData from '../../../state/defaults/defaultClusterFormData'; export default ({ port, setPort }) => { const alert = useAlert(); diff --git a/src/components/instance/clustering/setup_user.js b/src/components/instance/clustering/setup_user.js index 53e51b9bf..8d8f893b5 100644 --- a/src/components/instance/clustering/setup_user.js +++ b/src/components/instance/clustering/setup_user.js @@ -3,7 +3,7 @@ import { Row, Col, Button, Input } from '@nio/ui-kit'; import useAsyncEffect from 'use-async-effect'; import { useAlert } from 'react-alert'; -import defaultFormData from '../../../util/state/defaultClusterFormData'; +import defaultFormData from '../../../state/defaults/defaultClusterFormData'; import createClusterUser from '../../../api/instance/createClusterUser'; export default ({ clusterUser, clusterRole, auth, refreshInstance }) => { diff --git a/src/components/instance/config/updateInstanceForm.js b/src/components/instance/config/updateInstanceForm.js index 2536f0b2a..6ed75c026 100644 --- a/src/components/instance/config/updateInstanceForm.js +++ b/src/components/instance/config/updateInstanceForm.js @@ -2,12 +2,12 @@ import React, { useState } from 'react'; import { Button, Card, CardBody, Input, RadioCheckbox } from '@nio/ui-kit'; import useAsyncEffect from 'use-async-effect'; -import useLMS from '../../../stores/lmsData'; +import useLMS from '../../../state/lmsData'; import updateInstance from '../../../api/lms/updateInstance'; import updateLicense from '../../../api/lms/updateLicense'; import setLicense from '../../../api/instance/setLicense'; -import defaultLMSData from '../../../util/state/defaultLMSData'; -import defaultInstanceFormData from '../../../util/state/defaultInstanceFormData'; +import defaultLMSData from '../../../state/defaults/defaultLMSData'; +import defaultInstanceFormData from '../../../state/defaults/defaultInstanceFormData'; export default ({ instanceAuth, details, refreshInstance }) => { const [{ auth, products }] = useLMS(defaultLMSData); diff --git a/src/components/instance/index.js b/src/components/instance/index.js index bb5de6fc7..98b34ca93 100644 --- a/src/components/instance/index.js +++ b/src/components/instance/index.js @@ -2,13 +2,13 @@ import React, { useState } from 'react'; import { Route, Switch, useParams } from 'react-router-dom'; import useAsyncEffect from 'use-async-effect'; -import SubNav from './subnav'; -import routes from '../../routes/instance'; -import useLMS from '../../stores/lmsData'; -import useInstanceAuth from '../../stores/instanceAuths'; +import SubNav from '../shared/subnav'; +import routes from './routes'; +import useLMS from '../../state/lmsData'; +import useInstanceAuth from '../../state/instanceAuths'; import buildActiveInstanceObject from '../../util/buildActiveInstanceObject'; -import defaultLMSData from '../../util/state/defaultLMSData'; -import defaultActiveInstance from '../../util/state/defaultActiveInstance'; +import defaultLMSData from '../../state/defaults/defaultLMSData'; +import defaultActiveInstance from '../../state/defaults/defaultActiveInstance'; import getInstances from '../../api/lms/getInstances'; import getLicenses from '../../api/lms/getLicenses'; import getProducts from '../../api/lms/getProducts'; diff --git a/src/components/instance/routes.js b/src/components/instance/routes.js new file mode 100644 index 000000000..0b78979fa --- /dev/null +++ b/src/components/instance/routes.js @@ -0,0 +1,13 @@ +import Browse from './browse'; +import Clustering from './clustering'; +import Config from './config'; +import Users from './users'; +import Roles from './roles'; + +export default [ + { component: Browse, path: '/instances/:instance_id/browse/:schema?/:table?/:action?/:hash?', link: 'browse', icon: 'list' }, + { component: Clustering, path: '/instances/:instance_id/clustering/:schema?/:table?', link: 'clustering', icon: 'share-alt' }, + { component: Config, path: '/instances/:instance_id/config', link: 'config', icon: 'wrench' }, + { component: Users, path: '/instances/:instance_id/users', link: 'users', icon: 'users' }, + { component: Roles, path: '/instances/:instance_id/roles', link: 'roles', icon: 'check-square' }, +]; diff --git a/src/components/instance/subnav.js b/src/components/instance/subnav.js deleted file mode 100644 index 580107bf7..000000000 --- a/src/components/instance/subnav.js +++ /dev/null @@ -1,15 +0,0 @@ -import React from 'react'; -import { Navbar, Nav, NavItem } from '@nio/ui-kit'; -import { NavLink } from 'react-router-dom'; - -export default ({ routes = [], instanceId }) => ( - - - -); diff --git a/src/components/instances/index.js b/src/components/instances/index.js index a39d43536..a85883c92 100644 --- a/src/components/instances/index.js +++ b/src/components/instances/index.js @@ -2,12 +2,11 @@ import React from 'react'; import { Row } from '@nio/ui-kit'; import useAsyncEffect from 'use-async-effect'; -import TopNav from '../topnav'; -import useInstanceAuth from '../../stores/instanceAuths'; -import InstanceCard from './instanceCard'; -import NewInstanceCard from './newInstanceCard'; -import useLMS from '../../stores/lmsData'; -import defaultLMSData from '../../util/state/defaultLMSData'; +import useInstanceAuth from '../../state/instanceAuths'; +import InstanceCard from './list/instanceCard'; +import NewInstanceCard from './list/newInstanceCard'; +import useLMS from '../../state/lmsData'; +import defaultLMSData from '../../state/defaults/defaultLMSData'; import getInstances from '../../api/lms/getInstances'; import getLicenses from '../../api/lms/getLicenses'; import getProducts from '../../api/lms/getProducts'; @@ -24,19 +23,16 @@ export default () => { }, []); return ( - <> - - - - {lmsData.instances.map((i) => ( - setInstanceAuths({ ...instanceAuths, [id]: user && pass ? { user, pass } : false })} - /> - ))} - - + + + {lmsData.instances.map((i) => ( + setInstanceAuths({ ...instanceAuths, [id]: user && pass ? { user, pass } : false })} + /> + ))} + ); }; diff --git a/src/components/instances/cardBack.js b/src/components/instances/list/cardBack.js similarity index 94% rename from src/components/instances/cardBack.js rename to src/components/instances/list/cardBack.js index d14af01a1..e2184fbd6 100644 --- a/src/components/instances/cardBack.js +++ b/src/components/instances/list/cardBack.js @@ -2,8 +2,8 @@ import React, { useState } from 'react'; import { Button, Card, CardBody, Input, Row, Col } from '@nio/ui-kit'; import useAsyncEffect from 'use-async-effect'; import { useAlert } from 'react-alert'; -import queryInstance from '../../api/queryInstance'; -import defaultFormData from '../../util/state/defaultAuthFormData'; +import queryInstance from '../../../api/queryInstance'; +import defaultFormData from '../../../state/defaults/defaultAuthFormData'; export default ({ id, host, port, is_ssl, setAuth, flipCard, flipState }) => { const alert = useAlert(); diff --git a/src/components/instances/cardFront.js b/src/components/instances/list/cardFront.js similarity index 100% rename from src/components/instances/cardFront.js rename to src/components/instances/list/cardFront.js diff --git a/src/components/instances/instanceCard.js b/src/components/instances/list/instanceCard.js similarity index 100% rename from src/components/instances/instanceCard.js rename to src/components/instances/list/instanceCard.js diff --git a/src/components/instances/newInstanceCard.js b/src/components/instances/list/newInstanceCard.js similarity index 92% rename from src/components/instances/newInstanceCard.js rename to src/components/instances/list/newInstanceCard.js index 160af5814..4e06a7891 100644 --- a/src/components/instances/newInstanceCard.js +++ b/src/components/instances/list/newInstanceCard.js @@ -1,7 +1,7 @@ import React, { useState } from 'react'; import { Card, CardBody, Col } from '@nio/ui-kit'; -import NewInstanceModal from './newInstanceModal'; +import NewInstanceModal from '../new/modal'; export default () => { const [showForm, setShowForm] = useState(false); diff --git a/src/components/instances/cloudInstanceForm.js b/src/components/instances/new/cloud.js similarity index 86% rename from src/components/instances/cloudInstanceForm.js rename to src/components/instances/new/cloud.js index 0d9d96046..85dc02bf3 100644 --- a/src/components/instances/cloudInstanceForm.js +++ b/src/components/instances/new/cloud.js @@ -1,7 +1,7 @@ import React, { useState, useEffect } from 'react'; import { Col, Input, Row, RadioCheckbox, Button, Card, CardBody } from '@nio/ui-kit'; -import defaultInstanceFormData from '../../util/state/defaultInstanceFormData'; +import defaultInstanceFormData from '../../../state/defaults/defaultInstanceFormData'; export default ({ products, regions, setInstanceDetails, needsCard }) => { const [formData, updateForm] = useState(defaultInstanceFormData); @@ -87,17 +87,6 @@ export default ({ products, regions, setInstanceDetails, needsCard }) => { />
-
Instance Cost
-
- updateForm({ ...formData, stripe_product_id: value })} - options={products} - value={formData.stripe_product_id || ''} - defaultValue={products[0]} - /> -
+
+ cloud
, + unchecked:
cloud
, + }} + checked={cloud} + onChange={(e) => setCloud(e.target.checked)} + /> +
+
+ setSearch(e.target.value)} + placeholder="filter instances" + value={search} + /> + {search && ( + + )} +
+ + ) : routes.map((route) => ( + + + + {route.link} + + + ))} + + +); diff --git a/src/util/filterInstances.js b/src/util/filterInstances.js new file mode 100644 index 000000000..834ac494e --- /dev/null +++ b/src/util/filterInstances.js @@ -0,0 +1,16 @@ +export default ({ local, cloud, search, instances }) => { + if (!local && !cloud) return []; + if (search === '' && local && cloud) return instances; + + return instances.filter((i) => { + if (!local && i.is_local) return false; + if (!cloud && !i.is_local) return false; + if (search === '') return true; + + let pass = false; + if (i.instance_name && i.instance_name.indexOf(search) !== -1) pass = true; + if (i.host && i.host.indexOf(search) !== -1) pass = true; + if (i.domain_name && i.domain_name.indexOf(search) !== -1) pass = true; + return pass; + }); +}; From 8b313aa6866510885497dbe017ca34b62e9c025f Mon Sep 17 00:00:00 2001 From: deliciousmonster Date: Wed, 26 Feb 2020 17:16:01 -0700 Subject: [PATCH 16/19] formatting --- src/components/account/profile/password.js | 21 +++++++++++++-------- src/components/account/profile/profile.js | 21 +++++++++++++-------- src/components/account/routes.js | 2 +- src/components/account/users/add.js | 2 +- 4 files changed, 28 insertions(+), 18 deletions(-) diff --git a/src/components/account/profile/password.js b/src/components/account/profile/password.js index dc31ae4b9..520c607f1 100644 --- a/src/components/account/profile/password.js +++ b/src/components/account/profile/password.js @@ -58,19 +58,24 @@ export default () => {

+ + + + + + {passwordForm.error && (
- {passwordForm.error}
+ {passwordForm.error}
)} - ); }; diff --git a/src/components/account/profile/profile.js b/src/components/account/profile/profile.js index 7ba0653c3..c921c0a42 100644 --- a/src/components/account/profile/profile.js +++ b/src/components/account/profile/profile.js @@ -61,19 +61,24 @@ export default () => {
+ + + + + + {profileForm.error && (
- {profileForm.error}
+ {profileForm.error}
)} - ); }; diff --git a/src/components/account/routes.js b/src/components/account/routes.js index 8e0d6c33a..cd6ffe868 100644 --- a/src/components/account/routes.js +++ b/src/components/account/routes.js @@ -4,6 +4,6 @@ import Billing from './billing'; export default [ { component: Profile, path: '/account/profile', link: 'profile', icon: 'user' }, - { component: Users, path: '/account/users/:hash?', link: 'users', icon: 'users' }, { component: Billing, path: '/account/billing', link: 'billing', icon: 'credit-card-alt' }, + { component: Users, path: '/account/users/:hash?', link: 'users', icon: 'users' }, ]; diff --git a/src/components/account/users/add.js b/src/components/account/users/add.js index de134dfa3..8dc934c44 100644 --- a/src/components/account/users/add.js +++ b/src/components/account/users/add.js @@ -66,7 +66,7 @@ export default ({ setLastUpdate, customerId }) => { block onClick={() => updateUserForm({ ...userForm, submitted: true, error: false })} > - Save Profile + Add User {userForm.error && (
From 34e3240d025c4706641ec7cfeb1b7dea089a1874 Mon Sep 17 00:00:00 2001 From: deliciousmonster Date: Wed, 26 Feb 2020 17:26:38 -0700 Subject: [PATCH 17/19] linter --- .eslintrc | 2 +- src/api/instance/getTableData.js | 4 +-- src/api/instance/getTotalRecords.js | 2 +- src/api/lms/updateInstance.js | 2 +- src/assets/styles/_base.scss | 15 ++++---- src/assets/styles/_card.scss | 1 + src/assets/styles/_clustering.scss | 1 + src/assets/styles/_forms.scss | 35 ++++++++++--------- src/assets/styles/_login.scss | 2 +- src/assets/styles/_modal.scss | 2 +- src/assets/styles/_react-table.scss | 3 +- src/components/account/billing/invoices.js | 2 +- .../instance/clustering/setup_enable.js | 5 +-- .../instance/config/updateInstanceForm.js | 5 ++- src/components/instances/new/cloud.js | 2 +- src/util/handleSchemaTableRedirect.js | 2 +- 16 files changed, 44 insertions(+), 41 deletions(-) diff --git a/.eslintrc b/.eslintrc index 77d1940da..02e2d62b0 100644 --- a/.eslintrc +++ b/.eslintrc @@ -10,7 +10,7 @@ "rules": { "strict": 0, "react-hooks/rules-of-hooks": "error", - "react-hooks/exhaustive-deps": "warn", + "react-hooks/exhaustive-deps": 0, "camelcase": 0, "array-callback-return": 0, "react/require-extension": 0, diff --git a/src/api/instance/getTableData.js b/src/api/instance/getTableData.js index 02f5b023a..74194ee94 100644 --- a/src/api/instance/getTableData.js +++ b/src/api/instance/getTableData.js @@ -14,7 +14,7 @@ export default async ({ schema, table, tableState, auth }) => { [{ newTotalRecords }] = await queryInstance({ operation: 'sql', sql: countSQL }, auth); newTotalPages = newTotalRecords && Math.ceil(newTotalRecords / tableState.pageSize); } catch (e) { - console.log('Failed to get row count'); + // console.log('Failed to get row count'); fetchError = true; } @@ -25,7 +25,7 @@ export default async ({ schema, table, tableState, auth }) => { dataSQL += ` LIMIT ${(tableState.page * tableState.pageSize) + tableState.pageSize} OFFSET ${tableState.page * tableState.pageSize}`; newData = await queryInstance({ operation: 'sql', sql: dataSQL }, auth); } catch (e) { - console.log('Failed to get table data'); + // console.log('Failed to get table data'); fetchError = true; } diff --git a/src/api/instance/getTotalRecords.js b/src/api/instance/getTotalRecords.js index a6307ceec..ec6f718c4 100644 --- a/src/api/instance/getTotalRecords.js +++ b/src/api/instance/getTotalRecords.js @@ -5,7 +5,7 @@ export default async ({ schema, table, auth }) => { try { [{ newTotalRecords }] = await queryInstance({ operation: 'sql', sql: `SELECT count(*) as newTotalRecords FROM ${schema}.${table} ` }, auth); } catch (e) { - console.log('Failed to get row count'); + // console.log('Failed to get row count'); } return newTotalRecords; }; diff --git a/src/api/lms/updateInstance.js b/src/api/lms/updateInstance.js index 9e4c0c188..554e615c4 100644 --- a/src/api/lms/updateInstance.js +++ b/src/api/lms/updateInstance.js @@ -3,6 +3,6 @@ import queryLMS from '../queryLMS'; export default async ({ auth, instance_name, stripe_product_id, instance_id, customer_id }) => queryLMS({ endpoint: 'updateInstance', method: 'POST', - payload: { stripe_product_id, instance_id, customer_id }, + payload: { stripe_product_id, instance_id, customer_id, instance_name }, auth, }); diff --git a/src/assets/styles/_base.scss b/src/assets/styles/_base.scss index 7f9296ad7..d19775b9a 100644 --- a/src/assets/styles/_base.scss +++ b/src/assets/styles/_base.scss @@ -1,7 +1,7 @@ -$minWidth: 314px; +$min-width: 314px; body { - min-width: $minWidth; + min-width: $min-width; overflow-x: hidden; #app-bg { @@ -17,10 +17,10 @@ body { } #app { - min-width: $minWidth; + min-width: $min-width; #app-container { - min-width: $minWidth; + min-width: $min-width; margin-top: 135px; padding: 0 2rem; height: calc(100vh - 135px); @@ -32,7 +32,7 @@ body { } #app-nav { - min-width: $minWidth; + min-width: $min-width; background: $gradient-purple-magenta !important; padding: 0 2rem; position: fixed; @@ -51,6 +51,7 @@ body { background-position: top left; background-size: contain; } + .nav-item { margin-left: 1.5rem; } @@ -58,7 +59,7 @@ body { #app-subnav { width: 100%; - min-width: $minWidth; + min-width: $min-width; background: $light-grey-overlay !important; padding: 5px 2rem; position: fixed; @@ -80,7 +81,7 @@ body { @media screen and (max-width: 991px) { padding: 0 5px; - font-size: 0.875rem; + font-size: .875rem; } diff --git a/src/assets/styles/_card.scss b/src/assets/styles/_card.scss index b663cc95e..2920398a2 100644 --- a/src/assets/styles/_card.scss +++ b/src/assets/styles/_card.scss @@ -2,6 +2,7 @@ text-shadow: 0 2px 3px $grey-overlay; color: $color-white !important; } + .card { background: $light-white-overlay !important; color: $color-black; diff --git a/src/assets/styles/_clustering.scss b/src/assets/styles/_clustering.scss index 1c7d3dd1f..71b6c47e9 100644 --- a/src/assets/styles/_clustering.scss +++ b/src/assets/styles/_clustering.scss @@ -16,6 +16,7 @@ width: 20%; } } + .config-row { padding-top: 15px; padding-bottom: 15px; diff --git a/src/assets/styles/_forms.scss b/src/assets/styles/_forms.scss index 8ae3a759e..f7d09bdf9 100644 --- a/src/assets/styles/_forms.scss +++ b/src/assets/styles/_forms.scss @@ -6,7 +6,7 @@ input:-webkit-autofill, input:-webkit-autofill:hover, input:-webkit-autofill:focus, input:-webkit-autofill:active { - border: 1px solid $color-lightgrey!important; + border: 1px solid $color-lightgrey !important; background: $light-white-overlay !important; color: $color-black; font-family: $font-family-sans; @@ -31,13 +31,14 @@ input:-webkit-autofill:active { } } - &.outline{ + &.outline { &-dark { border-color: transparent !important; background: $faint-white-overlay !important; color: $color-white !important; width: 100%; max-width: 280px; + &::placeholder { color: $white-overlay !important; } @@ -64,30 +65,32 @@ input:-webkit-autofill:active { font-weight: 100 !important; @media screen and (max-width: 575px) { - font-size: 0.75rem !important; + font-size: .75rem !important; } } } .react-toggle-thumb { - &:after { + &::after { position: absolute; top: 3px; left: 0; right: 0; font-size: 14px; - content: "\f00d"; - font-family: 'FontAwesome'!important; + content: '\f00d'; + font-family: 'FontAwesome' !important; color: $color-danger; } } + .react-toggle--checked { .react-toggle-track { background-color: $faint-white-overlay !important; } + .react-toggle-thumb { - &:after { - content: "\f00c"; + &::after { + content: '\f00c'; color: $color-success; } } @@ -103,7 +106,7 @@ input:-webkit-autofill:active { .clear-filter { background: none !important; - border: none !important; + border: 0 !important; position: absolute; right: 8px; top: 0; @@ -119,18 +122,18 @@ textarea { } .stripe-input-holder { - padding: 8px 12px 0 12px; + padding: 8px 12px 0; text-align: left; } -.nio-check-mark:before { - content: "\f00c"; +.nio-check-mark::before { + content: '\f00c'; } -.nio:before { - font-family: 'FontAwesome'!important; +.nio::before { + font-family: 'FontAwesome' !important; font-style: normal; - font-weight: 400!important; + font-weight: 400 !important; font-variant: normal; text-transform: none; vertical-align: top; @@ -144,7 +147,7 @@ textarea { white-space: nowrap; &:last-child { - border-bottom: none; + border-bottom: 0; padding-bottom: 4px; } } diff --git a/src/assets/styles/_login.scss b/src/assets/styles/_login.scss index 3c672a9e6..548d2d679 100644 --- a/src/assets/styles/_login.scss +++ b/src/assets/styles/_login.scss @@ -27,7 +27,7 @@ text-align: center !important; background: $white-overlay !important; color: $color-white !important; - border: none !important; + border: 0 !important; &::placeholder { text-align: center !important; diff --git a/src/assets/styles/_modal.scss b/src/assets/styles/_modal.scss index 74707417c..511c7aa5e 100644 --- a/src/assets/styles/_modal.scss +++ b/src/assets/styles/_modal.scss @@ -10,7 +10,7 @@ .instance-form-card-holder { - &:first-child{ + &:first-child { @media screen and (max-width: 991px) { margin-bottom: 1rem; } diff --git a/src/assets/styles/_react-table.scss b/src/assets/styles/_react-table.scss index e0d4a6c40..8de85478d 100755 --- a/src/assets/styles/_react-table.scss +++ b/src/assets/styles/_react-table.scss @@ -27,7 +27,7 @@ .rt-td, .rt-th { text-align: left !important; - border-right: none !important; + border-right: 0 !important; &.-sort-desc { box-shadow: inset 0 -2px 0 0 $color-black; @@ -87,6 +87,7 @@ .-pageInfo { margin-left: 0; } + .-pageSizeOptions { margin-right: 0; } diff --git a/src/components/account/billing/invoices.js b/src/components/account/billing/invoices.js index d5a2cd94e..8f777ce48 100644 --- a/src/components/account/billing/invoices.js +++ b/src/components/account/billing/invoices.js @@ -29,4 +29,4 @@ export default () => {
)); -} +}; diff --git a/src/components/instance/clustering/setup_enable.js b/src/components/instance/clustering/setup_enable.js index e645299ca..4a820e476 100644 --- a/src/components/instance/clustering/setup_enable.js +++ b/src/components/instance/clustering/setup_enable.js @@ -8,10 +8,7 @@ export default ({ port, username, instanceId, auth, refreshInstance }) => { const [submitted, setSubmitted] = useState(false); useAsyncEffect(async () => { - if (submitted) { - const result = await configureCluster({ port, username, instanceId, auth, refreshInstance }); - console.log(result); - } + if (submitted) await configureCluster({ port, username, instanceId, auth, refreshInstance }); }, [submitted]); return ( diff --git a/src/components/instance/config/updateInstanceForm.js b/src/components/instance/config/updateInstanceForm.js index e2f4ddbe8..74410d69e 100644 --- a/src/components/instance/config/updateInstanceForm.js +++ b/src/components/instance/config/updateInstanceForm.js @@ -21,9 +21,8 @@ export default ({ instanceAuth, details, refreshInstance }) => { const { instance_id, customer_id, license_id, fingerprint } = details; const newLicense = await updateLicense({ auth, payload: { license_id, stripe_product_id, instance_id, customer_id, fingerprint } }); - const licenseApplied = await setLicense({ auth: instanceAuth, key: newLicense.key, company: newLicense.company }); - const newInstance = await updateInstance({ auth, payload: { stripe_product_id, instance_id, customer_id, instance_name } }); - console.log(newLicense, licenseApplied, newInstance); + await setLicense({ auth: instanceAuth, key: newLicense.key, company: newLicense.company }); + await updateInstance({ auth, payload: { stripe_product_id, instance_id, customer_id, instance_name } }); updateForm({ ...formData, submitted: false }); refreshInstance(Date.now()); } diff --git a/src/components/instances/new/cloud.js b/src/components/instances/new/cloud.js index 85dc02bf3..60b280f6e 100644 --- a/src/components/instances/new/cloud.js +++ b/src/components/instances/new/cloud.js @@ -12,7 +12,7 @@ export default ({ products, regions, setInstanceDetails, needsCard }) => { formData.is_ssl = true; setInstanceDetails(formData); } - }, [formData]); + }, [formData, setInstanceDetails]); useEffect(() => { updateForm({ ...formData, stripe_product_id: products[0].value, instance_region: regions[0].value }); diff --git a/src/util/handleSchemaTableRedirect.js b/src/util/handleSchemaTableRedirect.js index afbce4c7c..a96f03347 100644 --- a/src/util/handleSchemaTableRedirect.js +++ b/src/util/handleSchemaTableRedirect.js @@ -14,4 +14,4 @@ export default ({ entities, instance_id, schema, table, history, targetPath }) = default: break; } -} +}; From 9c8541f386a202a63c08e6fb73f829966c8604d2 Mon Sep 17 00:00:00 2001 From: deliciousmonster Date: Fri, 28 Feb 2020 21:19:01 -0700 Subject: [PATCH 18/19] new Products shape. --- .eslintrc | 2 +- mock_data/LMS_API.instances.json | 62 +- mock_data/LMS_API.licenses.json | 40 +- mock_data/LMS_API.products.json | 378 +++- package-lock.json | 1990 ++++------------- package.json | 15 +- src/api/lms/getProducts.js | 38 +- src/api/lms/updateInstance.js | 4 +- src/assets/styles/_base.scss | 4 +- src/assets/styles/_button.scss | 8 +- src/assets/styles/_card.scss | 10 +- src/assets/styles/_forms.scss | 16 +- src/components/account/billing/cardEdit.js | 28 +- src/components/account/billing/index.js | 2 +- src/components/account/billing/invoices.js | 2 +- src/components/account/index.js | 2 +- src/components/account/users/index.js | 2 +- src/components/app.js | 2 +- src/components/instance/browse/csvuploader.js | 9 +- src/components/instance/clustering/manage.js | 2 +- src/components/instance/config/index.js | 40 +- .../instance/config/instanceDetails.js | 156 +- .../instance/config/updateInstanceForm.js | 177 +- src/components/instance/index.js | 6 +- src/components/instances/index.js | 12 +- src/components/instances/list/cardBack.js | 8 +- src/components/instances/list/cardFront.js | 12 +- src/components/instances/new/cloud.js | 143 +- src/components/instances/new/confirm.js | 243 +- src/components/instances/new/local.js | 166 +- src/components/instances/new/meta.js | 172 ++ src/components/instances/new/modal.js | 115 +- src/components/instances/new/payment.js | 191 +- src/components/instances/new/type.js | 109 +- src/components/{login.js => login/index.js} | 12 +- src/components/{shared => navs}/subnav.js | 0 src/components/{ => navs}/topnav.js | 4 +- src/components/shared/entityManager.js | 4 +- src/state/defaults/defaultInstanceFormData.js | 9 - src/state/{ => stores}/instanceAuths.js | 0 src/state/{ => stores}/lmsData.js | 0 src/util/addInstanceSteps.js | 20 + src/util/buildActiveInstanceObject.js | 5 +- src/util/stripe/getInstancePrice.js | 4 +- 44 files changed, 1880 insertions(+), 2344 deletions(-) create mode 100644 src/components/instances/new/meta.js rename src/components/{login.js => login/index.js} (87%) rename src/components/{shared => navs}/subnav.js (100%) rename src/components/{ => navs}/topnav.js (93%) delete mode 100644 src/state/defaults/defaultInstanceFormData.js rename src/state/{ => stores}/instanceAuths.js (100%) rename src/state/{ => stores}/lmsData.js (100%) create mode 100644 src/util/addInstanceSteps.js diff --git a/.eslintrc b/.eslintrc index 02e2d62b0..cdb8323c3 100644 --- a/.eslintrc +++ b/.eslintrc @@ -34,6 +34,6 @@ "jsx-a11y/click-events-have-key-events": 0, "jsx-a11y/anchor-is-valid": 0, "no-param-reassign": 0, - "max-lines": ["warn", {"max": 150, "skipComments": false}] + "max-lines": ["warn", {"max": 135, "skipComments": false}] } } diff --git a/mock_data/LMS_API.instances.json b/mock_data/LMS_API.instances.json index 62d535d9f..5a56de647 100644 --- a/mock_data/LMS_API.instances.json +++ b/mock_data/LMS_API.instances.json @@ -12,7 +12,11 @@ "domain_name": null, "elastic_ip_address": null, "customer_id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", - "stripe_product_id": "product_1" + "stripe_product_id": "prod_GoB3yLzygPeGMu", + "stripe_plan_id": "plan_GoB4aP5U6PuR6u", + "stripe_storage_product_id": null, + "stripe_storage_plan_id": null, + "storage_qty_gb": null }, { "id": "4f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20de", @@ -23,11 +27,15 @@ "is_ssl": false, "is_local": false, "status": "ready", - "instance_region": "us-east", + "instance_region": "us-east-1", "domain_name": "4f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20de.cloud.harperdb.io", "elastic_ip_address": "172.168.2.12", "customer_id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", - "stripe_product_id": "product_1" + "stripe_product_id": "prod_Gh1XXQx6J8YaJl", + "stripe_plan_id": "plan_GoB54KxG5JRkwm", + "stripe_storage_product_id": "prod_GoUJnVwOYvTjU9", + "stripe_storage_plan_id": "plan_GoUmLEBX2KIiaF", + "storage_qty_gb": 10 }, { "id": "cee98941-8975-44d6-88e6-9a1a4fa02b25", @@ -38,11 +46,15 @@ "is_ssl": false, "is_local": false, "status": "ready", - "instance_region": "us-east", + "instance_region": "us-east-1", "domain_name": "cee98941-8975-44d6-88e6-9a1a4fa02b25.cloud.harperdb.io", "elastic_ip_address": "172.168.2.12", "customer_id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", - "stripe_product_id": "product_1" + "stripe_product_id": "prod_Gh1XXQx6J8YaJl", + "stripe_plan_id": "2GB_Beta", + "stripe_storage_product_id": "prod_GoUJnVwOYvTjU9", + "stripe_storage_plan_id": "plan_GoUmLEBX2KIiaF", + "storage_qty_gb": 100 }, { "id": "a32b2464-b5ca-4b9d-a10f-c6da23a7dcbe", @@ -53,11 +65,15 @@ "is_ssl": false, "is_local": false, "status": "ready", - "instance_region": "us-east", + "instance_region": "us-east-1", "domain_name": "a32b2464-b5ca-4b9d-a10f-c6da23a7dcbe.cloud.harperdb.io", "elastic_ip_address": "172.168.2.12", "customer_id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", - "stripe_product_id": "product_1" + "stripe_product_id": "prod_Gh1XXQx6J8YaJl", + "stripe_plan_id": "4GB_Beta", + "stripe_storage_product_id": "prod_GoUJnVwOYvTjU9", + "stripe_storage_plan_id": "plan_GoUmLEBX2KIiaF", + "storage_qty_gb": 250 }, { "id": "7a60b51d-7983-4659-a34b-cfb578a4db54", @@ -68,11 +84,15 @@ "is_ssl": false, "is_local": false, "status": "ready", - "instance_region": "us-east", + "instance_region": "us-east-1", "domain_name": "7a60b51d-7983-4659-a34b-cfb578a4db54.cloud.harperdb.io", "elastic_ip_address": "172.168.2.12", "customer_id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", - "stripe_product_id": "product_1" + "stripe_product_id": "prod_Gh1XXQx6J8YaJl", + "stripe_plan_id": "8GB_Beta", + "stripe_storage_product_id": "prod_GoUJnVwOYvTjU9", + "stripe_storage_plan_id": "plan_GoUmLEBX2KIiaF", + "storage_qty_gb": 500 }, { "id": "f0d29404-9fb1-4d66-ab98-41f05e1ac665", @@ -83,11 +103,15 @@ "is_ssl": false, "is_local": false, "status": "ready", - "instance_region": "us-east", + "instance_region": "us-east-1", "domain_name": "f0d29404-9fb1-4d66-ab98-41f05e1ac665.cloud.harperdb.io", "elastic_ip_address": "172.168.2.12", "customer_id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", - "stripe_product_id": "product_1" + "stripe_product_id": "prod_Gh1XXQx6J8YaJl", + "stripe_plan_id": "4GB_Beta", + "stripe_storage_product_id": "prod_GoUJnVwOYvTjU9", + "stripe_storage_plan_id": "plan_GoUmLEBX2KIiaF", + "storage_qty_gb": 1000 }, { "id": "ccf2b958-0b0e-4bf6-a81d-6abee1e4eec5", @@ -98,11 +122,15 @@ "is_ssl": false, "is_local": false, "status": "ready", - "instance_region": "us-east", + "instance_region": "us-east-1", "domain_name": "ccf2b958-0b0e-4bf6-a81d-6abee1e4eec5.cloud.harperdb.io", "elastic_ip_address": "172.168.2.12", "customer_id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", - "stripe_product_id": "product_1" + "stripe_product_id": "prod_Gh1XXQx6J8YaJl", + "stripe_plan_id": "2GB_Beta", + "stripe_storage_product_id": "prod_GoUJnVwOYvTjU9", + "stripe_storage_plan_id": "plan_GoUmLEBX2KIiaF", + "storage_qty_gb": 500 }, { "id": "8291c224-6911-4155-b5a9-34559e5a45ff", @@ -113,10 +141,14 @@ "is_ssl": false, "is_local": false, "status": "ready", - "instance_region": "us-east", + "instance_region": "us-east-1", "domain_name": "8291c224-6911-4155-b5a9-34559e5a45ff.cloud.harperdb.io", "elastic_ip_address": "172.168.2.12", "customer_id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", - "stripe_product_id": "product_1" + "stripe_product_id": "prod_Gh1XXQx6J8YaJl", + "stripe_plan_id": "plan_GoB54KxG5JRkwm", + "stripe_storage_product_id": "prod_GoUJnVwOYvTjU9", + "stripe_storage_plan_id": "plan_GoUmLEBX2KIiaF", + "storage_qty_gb": 250 } ] diff --git a/mock_data/LMS_API.licenses.json b/mock_data/LMS_API.licenses.json index 221a46de9..65bccff9b 100644 --- a/mock_data/LMS_API.licenses.json +++ b/mock_data/LMS_API.licenses.json @@ -3,88 +3,96 @@ "license_id": "license-guid-0", "instance_id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", "customer_id": "customer-id-guid", - "stripe_product_id": "product_1", "version": "2.0.14-BETA", "exp_date": 1582669908353, "storage_type": "LMDB", "ram_allocation": 1, - "fingerprint": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df" + "fingerprint": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", + "stripe_product_id": "prod_GoB3yLzygPeGMu", + "stripe_plan_id": "plan_GoB4aP5U6PuR6u" }, { "license_id": "license-guid-1", "instance_id": "4f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20de", "customer_id": "customer-id-guid", - "stripe_product_id": "product_1", "version": "2.0.14-BETA", "exp_date": 1582669908353, "storage_type": "LMDB", "ram_allocation": 1, - "fingerprint": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df" + "fingerprint": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", + "stripe_product_id": "prod_Gh1XXQx6J8YaJl", + "stripe_plan_id": "plan_GoB54KxG5JRkwm" }, { "license_id": "license-guid-2", "instance_id": "cee98941-8975-44d6-88e6-9a1a4fa02b25", "customer_id": "customer-id-guid", - "stripe_product_id": "product_1", "version": "2.0.14-BETA", "exp_date": 1582669908353, "storage_type": "LMDB", "ram_allocation": 1, - "fingerprint": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df" + "fingerprint": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", + "stripe_product_id": "prod_Gh1XXQx6J8YaJl", + "stripe_plan_id": "2GB_Beta" }, { "license_id": "license-guid-3", "instance_id": "a32b2464-b5ca-4b9d-a10f-c6da23a7dcbe", "customer_id": "customer-id-guid", - "stripe_product_id": "product_1", "version": "2.0.14-BETA", "exp_date": 1582669908353, "storage_type": "LMDB", "ram_allocation": 1, - "fingerprint": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df" + "fingerprint": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", + "stripe_product_id": "prod_Gh1XXQx6J8YaJl", + "stripe_plan_id": "4GB_Beta" }, { "license_id": "license-guid-4", "instance_id": "7a60b51d-7983-4659-a34b-cfb578a4db54", "customer_id": "customer-id-guid", - "stripe_product_id": "product_1", "version": "2.0.14-BETA", "exp_date": 1582669908353, "storage_type": "LMDB", "ram_allocation": 1, - "fingerprint": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df" + "fingerprint": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", + "stripe_product_id": "prod_Gh1XXQx6J8YaJl", + "stripe_plan_id": "8GB_Beta" }, { "license_id": "license-guid-5", "instance_id": "f0d29404-9fb1-4d66-ab98-41f05e1ac665", "customer_id": "customer-id-guid", - "stripe_product_id": "product_1", "version": "2.0.14-BETA", "exp_date": 1582669908353, "storage_type": "LMDB", "ram_allocation": 1, - "fingerprint": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df" + "fingerprint": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", + "stripe_product_id": "prod_Gh1XXQx6J8YaJl", + "stripe_plan_id": "4GB_Beta" }, { "license_id": "license-guid-6", "instance_id": "ccf2b958-0b0e-4bf6-a81d-6abee1e4eec5", "customer_id": "customer-id-guid", - "stripe_product_id": "product_1", "version": "2.0.14-BETA", "exp_date": 1582669908353, "storage_type": "LMDB", "ram_allocation": 1, - "fingerprint": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df" + "fingerprint": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", + "stripe_product_id": "prod_Gh1XXQx6J8YaJl", + "stripe_plan_id": "2GB_Beta" }, { "license_id": "license-guid-7", "instance_id": "8291c224-6911-4155-b5a9-34559e5a45ff", "customer_id": "customer-id-guid", - "stripe_product_id": "product_1", "version": "2.0.14-BETA", "exp_date": 1582669908353, "storage_type": "LMDB", "ram_allocation": 1, - "fingerprint": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df" + "fingerprint": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", + "stripe_product_id": "prod_Gh1XXQx6J8YaJl", + "stripe_plan_id": "plan_GoB54KxG5JRkwm" } ] diff --git a/mock_data/LMS_API.products.json b/mock_data/LMS_API.products.json index 74d5aacd1..7e32fe390 100644 --- a/mock_data/LMS_API.products.json +++ b/mock_data/LMS_API.products.json @@ -1,26 +1,352 @@ -[ - { - "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", - "stripe_product_id": "product_1", - "instance_ram": 1, - "instance_disk_space_gigs": 100, - "cloud_aws_instance_type": "AWS-Instance-Type-1", - "cloud_price_monthly": 0, - "cloud_price_annual": 0, - "local_price_monthly": 0, - "local_price_annual": 0, - "is_active": true - }, - { - "id": "3fa85f64-5717-4562-b3fc-2c963f66afa7", - "stripe_product_id": "product_2", - "instance_ram": 2, - "instance_disk_space_gigs": 100, - "cloud_aws_instance_type": "AWS-Instance-Type-2", - "cloud_price_monthly": 19.99, - "cloud_price_annual": 199.99, - "local_price_monthly": 9.99, - "local_price_annual": 99.99, - "is_active": true - } -] +{ + "statusCode": 200, + "body": [ + { + "id": "prod_GoUJnVwOYvTjU9", + "object": "product", + "active": true, + "attributes": [], + "caption": null, + "created": 1582829029, + "deactivate_on": [], + "description": null, + "images": [], + "livemode": false, + "metadata": {}, + "name": "HarperDB Cloud Storage", + "package_dimensions": null, + "shippable": null, + "statement_descriptor": null, + "type": "service", + "unit_label": "GB", + "updated": 1582830761, + "url": null, + "plans": [ + { + "id": "plan_GoUmLEBX2KIiaF", + "object": "plan", + "active": true, + "aggregate_usage": null, + "amount": "$0.00", + "amount_decimal": null, + "billing_scheme": "tiered", + "created": 1582830760, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "General Purpose SSD", + "product": "prod_GoUJnVwOYvTjU9", + "tiers": [ + { + "flat_amount": null, + "flat_amount_decimal": null, + "unit_amount": 0, + "unit_amount_decimal": "0", + "up_to": 10 + }, + { + "flat_amount": null, + "flat_amount_decimal": null, + "unit_amount": 25, + "unit_amount_decimal": "25", + "up_to": null + } + ], + "tiers_mode": "graduated", + "transform_usage": null, + "trial_period_days": null, + "usage_type": "licensed" + } + ] + }, + { + "id": "prod_GoB3yLzygPeGMu", + "object": "product", + "active": true, + "attributes": [], + "caption": null, + "created": 1582757367, + "deactivate_on": [], + "description": null, + "images": [], + "livemode": false, + "metadata": {}, + "name": "HarperDB Local Annual", + "package_dimensions": null, + "shippable": null, + "statement_descriptor": null, + "type": "service", + "unit_label": null, + "updated": 1582829172, + "url": null, + "plans": [ + { + "id": "plan_GoB4aP5U6PuR6u", + "object": "plan", + "active": true, + "aggregate_usage": null, + "amount": "$0.00", + "amount_decimal": "0", + "billing_scheme": "per_unit", + "created": 1582757478, + "currency": "usd", + "interval": "year", + "interval_count": 1, + "livemode": false, + "metadata": { + "instance_ram": "1GB" + }, + "nickname": "1GB", + "product": "prod_GoB3yLzygPeGMu", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "licensed" + }, + { + "id": "plan_GoB35E9tX0o6sq", + "object": "plan", + "active": true, + "aggregate_usage": null, + "amount": "$191.90", + "amount_decimal": "19190", + "billing_scheme": "per_unit", + "created": 1582757416, + "currency": "usd", + "interval": "year", + "interval_count": 1, + "livemode": false, + "metadata": { + "instance_ram": "2GB" + }, + "nickname": "2GB", + "product": "prod_GoB3yLzygPeGMu", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "licensed" + }, + { + "id": "plan_GoB4qG61roJhhk", + "object": "plan", + "active": true, + "aggregate_usage": null, + "amount": "$575.90", + "amount_decimal": "57590", + "billing_scheme": "per_unit", + "created": 1582757440, + "currency": "usd", + "interval": "year", + "interval_count": 1, + "livemode": false, + "metadata": { + "instance_ram": "4GB" + }, + "nickname": "4GB", + "product": "prod_GoB3yLzygPeGMu", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "licensed" + }, + { + "id": "plan_GoB47PZG32KA2I", + "object": "plan", + "active": true, + "aggregate_usage": null, + "amount": "$1681.92", + "amount_decimal": "168192", + "billing_scheme": "per_unit", + "created": 1582757462, + "currency": "usd", + "interval": "year", + "interval_count": 1, + "livemode": false, + "metadata": { + "instance_ram": "8GB" + }, + "nickname": "8GB", + "product": "prod_GoB3yLzygPeGMu", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "licensed" + } + ] + }, + { + "id": "prod_Gh1XXQx6J8YaJl", + "object": "product", + "active": true, + "attributes": [], + "caption": null, + "created": 1581107534, + "deactivate_on": [], + "description": null, + "images": [], + "livemode": false, + "metadata": {}, + "name": "HarperDB Cloud Beta", + "package_dimensions": null, + "shippable": null, + "statement_descriptor": null, + "type": "service", + "unit_label": null, + "updated": 1582829185, + "url": null, + "plans": [ + { + "id": "plan_GoB54KxG5JRkwm", + "object": "plan", + "active": true, + "aggregate_usage": null, + "amount": "$0.00", + "amount_decimal": "0", + "billing_scheme": "per_unit", + "created": 1582757496, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": { + "instance_ram": "1GB", + "instance_type": "t3.micro" + }, + "nickname": "1GB", + "product": "prod_Gh1XXQx6J8YaJl", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "licensed" + }, + { + "id": "2GB_Beta", + "object": "plan", + "active": true, + "aggregate_usage": null, + "amount": "$19.99", + "amount_decimal": "1999", + "billing_scheme": "per_unit", + "created": 1581107607, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": { + "instance_type": "t3.medium", + "instance_ram": "2GB" + }, + "nickname": "2GB", + "product": "prod_Gh1XXQx6J8YaJl", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "licensed" + }, + { + "id": "4GB_Beta", + "object": "plan", + "active": true, + "aggregate_usage": null, + "amount": "$59.99", + "amount_decimal": "5999", + "billing_scheme": "per_unit", + "created": 1581377198, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": { + "instance_type": "t3.medium", + "instance_ram": "4GB" + }, + "nickname": "4GB", + "product": "prod_Gh1XXQx6J8YaJl", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "licensed" + }, + { + "id": "8GB_Beta", + "object": "plan", + "active": true, + "aggregate_usage": null, + "amount": "$175.20", + "amount_decimal": "17520", + "billing_scheme": "per_unit", + "created": 1581377234, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": { + "instance_type": "m5.large", + "instance_ram": "8GB" + }, + "nickname": "8GB", + "product": "prod_Gh1XXQx6J8YaJl", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "licensed" + } + ] + }, + { + "id": "prod_GZ5dZoytHIPTfD", + "object": "product", + "active": true, + "attributes": [], + "caption": null, + "created": 1579277621, + "deactivate_on": [], + "description": null, + "images": [], + "livemode": false, + "metadata": {}, + "name": "HarperDB Cloud", + "package_dimensions": null, + "shippable": null, + "statement_descriptor": "HarperDB DBaaS", + "type": "service", + "unit_label": "hours", + "updated": 1582829183, + "url": null, + "plans": [ + { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": "$0.00", + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + } + ] + } + ] +} diff --git a/package-lock.json b/package-lock.json index d5167e8fc..e28b42eac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -39,9 +39,9 @@ } }, "@babel/compat-data": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.8.5.tgz", - "integrity": "sha512-jWYUqQX/ObOhG1UiEkbH5SANsE/8oKXiQWjj7p7xgj9Zmnt//aUvyz4dBkK0HNsS8/cbyC5NmmH87VekW+mXFg==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.8.6.tgz", + "integrity": "sha512-CurCIKPTkS25Mb8mz267vU95vy+TyUpnctEX2lV33xWNmHAfjruztgiPBbXZRh3xZZy1CYvGx6XfxyTVS+sk7Q==", "dev": true, "requires": { "browserslist": "^4.8.5", @@ -50,32 +50,32 @@ }, "dependencies": { "browserslist": { - "version": "4.8.6", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.8.6.tgz", - "integrity": "sha512-ZHao85gf0eZ0ESxLfCp73GG9O/VTytYDIkIiZDlURppLTI9wErSM/5yAKEq6rcUdxBLjMELmrYUJGg5sxGKMHg==", + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.9.0.tgz", + "integrity": "sha512-seffIXhwgB84+OCeT/aMjpZnsAsYDiMSC+CEs3UkF8iU64BZGYcu+TZYs/IBpo4nRi0vJywUJWYdbTsOhFTweg==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001023", - "electron-to-chromium": "^1.3.341", - "node-releases": "^1.1.47" + "caniuse-lite": "^1.0.30001030", + "electron-to-chromium": "^1.3.361", + "node-releases": "^1.1.50" } }, "caniuse-lite": { - "version": "1.0.30001025", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001025.tgz", - "integrity": "sha512-SKyFdHYfXUZf5V85+PJgLYyit27q4wgvZuf8QTOk1osbypcROihMBlx9GRar2/pIcKH2r4OehdlBr9x6PXetAQ==", + "version": "1.0.30001030", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001030.tgz", + "integrity": "sha512-QGK0W4Ft/Ac+zTjEiRJfwDNATvS3fodDczBXrH42784kcfqcDKpEPfN08N0HQjrAp8He/Jw8QiSS9QRn7XAbUw==", "dev": true }, "electron-to-chromium": { - "version": "1.3.345", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.345.tgz", - "integrity": "sha512-f8nx53+Z9Y+SPWGg3YdHrbYYfIJAtbUjpFfW4X1RwTZ94iUG7geg9tV8HqzAXX7XTNgyWgAFvce4yce8ZKxKmg==", + "version": "1.3.363", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.363.tgz", + "integrity": "sha512-4w19wPBkeunBjOA53lNFT36IdOD3Tk1OoIDtTX+VToJUUDX42QfuhtsNKXv25wmSnoBOExM3kTbj7/WDNBwHuQ==", "dev": true }, "node-releases": { - "version": "1.1.47", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.47.tgz", - "integrity": "sha512-k4xjVPx5FpwBUj0Gw7uvFOTF4Ep8Hok1I6qjwL3pLfwe7Y0REQSAqOwwv9TWBCUtMHxcXfY4PgRLRozcChvTcA==", + "version": "1.1.50", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.50.tgz", + "integrity": "sha512-lgAmPv9eYZ0bGwUYAKlr8MG6K4CvWliWqnkcT2P8mMAgVrH3lqfBPorFlxiG1pHQnqmavJZ9vbMXUTNyMLbrgQ==", "dev": true, "requires": { "semver": "^6.3.0" @@ -92,18 +92,18 @@ } }, "@babel/core": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.8.4.tgz", - "integrity": "sha512-0LiLrB2PwrVI+a2/IEskBopDYSd8BCb3rOvH7D5tzoWd696TBEduBvuLVm4Nx6rltrLZqvI3MCalB2K2aVzQjA==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.8.6.tgz", + "integrity": "sha512-Sheg7yEJD51YHAvLEV/7Uvw95AeWqYPL3Vk3zGujJKIhJ+8oLw2ALaf3hbucILhKsgSoADOvtKRJuNVdcJkOrg==", "dev": true, "requires": { "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.8.4", + "@babel/generator": "^7.8.6", "@babel/helpers": "^7.8.4", - "@babel/parser": "^7.8.4", - "@babel/template": "^7.8.3", - "@babel/traverse": "^7.8.4", - "@babel/types": "^7.8.3", + "@babel/parser": "^7.8.6", + "@babel/template": "^7.8.6", + "@babel/traverse": "^7.8.6", + "@babel/types": "^7.8.6", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.1", @@ -123,47 +123,6 @@ "@babel/highlight": "^7.8.3" } }, - "@babel/generator": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.4.tgz", - "integrity": "sha512-PwhclGdRpNAf3IxZb0YVuITPZmmrXz9zf6fH8lT4XbrmfQKr6ryBzhv593P5C6poJRciFCL/eHGW2NuGrgEyxA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3", - "jsesc": "^2.5.1", - "lodash": "^4.17.13", - "source-map": "^0.5.0" - } - }, - "@babel/helper-function-name": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz", - "integrity": "sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.8.3", - "@babel/template": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", - "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz", - "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3" - } - }, "@babel/highlight": { "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz", @@ -175,40 +134,6 @@ "js-tokens": "^4.0.0" } }, - "@babel/parser": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", - "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", - "dev": true - }, - "@babel/template": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.3.tgz", - "integrity": "sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/parser": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/traverse": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.4.tgz", - "integrity": "sha512-NGLJPZwnVEyBPLI+bl9y9aSnxMhsKz42so7ApAv9D+b4vAFPpY013FTS9LdKxcABoIYFU52HcYga1pPlx454mg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.8.4", - "@babel/helper-function-name": "^7.8.3", - "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/parser": "^7.8.4", - "@babel/types": "^7.8.3", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.13" - } - }, "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", @@ -256,23 +181,15 @@ } }, "@babel/generator": { - "version": "7.7.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.7.0.tgz", - "integrity": "sha512-1wdJ6UxHyL1XoJQ119JmvuRX27LRih7iYStMPZOWAjQqeAabFg3dYXKMpgihma+to+0ADsTVVt6oRyUxWZw6Mw==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.6.tgz", + "integrity": "sha512-4bpOR5ZBz+wWcMeVtcf7FbjcFzCp+817z2/gHNncIRcM9MmKzUhtWCYAq27RAfUrAFwb+OCG1s9WEaVxfi6cjg==", "dev": true, "requires": { - "@babel/types": "^7.7.0", + "@babel/types": "^7.8.6", "jsesc": "^2.5.1", "lodash": "^4.17.13", "source-map": "^0.5.0" - }, - "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } } }, "@babel/helper-annotate-as-pure": { @@ -313,156 +230,15 @@ "@babel/helper-hoist-variables": "^7.8.3", "@babel/traverse": "^7.8.3", "@babel/types": "^7.8.3" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", - "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", - "dev": true, - "requires": { - "@babel/highlight": "^7.8.3" - } - }, - "@babel/generator": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.4.tgz", - "integrity": "sha512-PwhclGdRpNAf3IxZb0YVuITPZmmrXz9zf6fH8lT4XbrmfQKr6ryBzhv593P5C6poJRciFCL/eHGW2NuGrgEyxA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3", - "jsesc": "^2.5.1", - "lodash": "^4.17.13", - "source-map": "^0.5.0" - } - }, - "@babel/helper-function-name": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz", - "integrity": "sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.8.3", - "@babel/template": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", - "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz", - "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/highlight": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz", - "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==", - "dev": true, - "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", - "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", - "dev": true - }, - "@babel/template": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.3.tgz", - "integrity": "sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/parser": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/traverse": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.4.tgz", - "integrity": "sha512-NGLJPZwnVEyBPLI+bl9y9aSnxMhsKz42so7ApAv9D+b4vAFPpY013FTS9LdKxcABoIYFU52HcYga1pPlx454mg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.8.4", - "@babel/helper-function-name": "^7.8.3", - "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/parser": "^7.8.4", - "@babel/types": "^7.8.3", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.13" - } - }, - "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" - } - }, - "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" - } - }, - "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 - }, - "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" - } - } } }, "@babel/helper-compilation-targets": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.8.4.tgz", - "integrity": "sha512-3k3BsKMvPp5bjxgMdrFyq0UaEO48HciVrOVF0+lon8pp95cyJ2ujAh0TrBHNMnJGT2rr0iKOJPFFbSqjDyf/Pg==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.8.6.tgz", + "integrity": "sha512-UrJdk27hKVJSnibFcUWYLkCL0ZywTUoot8yii1lsHJcvwrypagmYKjHLMWivQPm4s6GdyygCL8fiH5EYLxhQwQ==", "dev": true, "requires": { - "@babel/compat-data": "^7.8.4", + "@babel/compat-data": "^7.8.6", "browserslist": "^4.8.5", "invariant": "^2.2.4", "levenary": "^1.1.1", @@ -470,32 +246,32 @@ }, "dependencies": { "browserslist": { - "version": "4.8.6", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.8.6.tgz", - "integrity": "sha512-ZHao85gf0eZ0ESxLfCp73GG9O/VTytYDIkIiZDlURppLTI9wErSM/5yAKEq6rcUdxBLjMELmrYUJGg5sxGKMHg==", + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.9.0.tgz", + "integrity": "sha512-seffIXhwgB84+OCeT/aMjpZnsAsYDiMSC+CEs3UkF8iU64BZGYcu+TZYs/IBpo4nRi0vJywUJWYdbTsOhFTweg==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001023", - "electron-to-chromium": "^1.3.341", - "node-releases": "^1.1.47" + "caniuse-lite": "^1.0.30001030", + "electron-to-chromium": "^1.3.361", + "node-releases": "^1.1.50" } }, "caniuse-lite": { - "version": "1.0.30001025", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001025.tgz", - "integrity": "sha512-SKyFdHYfXUZf5V85+PJgLYyit27q4wgvZuf8QTOk1osbypcROihMBlx9GRar2/pIcKH2r4OehdlBr9x6PXetAQ==", + "version": "1.0.30001030", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001030.tgz", + "integrity": "sha512-QGK0W4Ft/Ac+zTjEiRJfwDNATvS3fodDczBXrH42784kcfqcDKpEPfN08N0HQjrAp8He/Jw8QiSS9QRn7XAbUw==", "dev": true }, "electron-to-chromium": { - "version": "1.3.345", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.345.tgz", - "integrity": "sha512-f8nx53+Z9Y+SPWGg3YdHrbYYfIJAtbUjpFfW4X1RwTZ94iUG7geg9tV8HqzAXX7XTNgyWgAFvce4yce8ZKxKmg==", + "version": "1.3.363", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.363.tgz", + "integrity": "sha512-4w19wPBkeunBjOA53lNFT36IdOD3Tk1OoIDtTX+VToJUUDX42QfuhtsNKXv25wmSnoBOExM3kTbj7/WDNBwHuQ==", "dev": true }, "node-releases": { - "version": "1.1.47", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.47.tgz", - "integrity": "sha512-k4xjVPx5FpwBUj0Gw7uvFOTF4Ep8Hok1I6qjwL3pLfwe7Y0REQSAqOwwv9TWBCUtMHxcXfY4PgRLRozcChvTcA==", + "version": "1.1.50", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.50.tgz", + "integrity": "sha512-lgAmPv9eYZ0bGwUYAKlr8MG6K4CvWliWqnkcT2P8mMAgVrH3lqfBPorFlxiG1pHQnqmavJZ9vbMXUTNyMLbrgQ==", "dev": true, "requires": { "semver": "^6.3.0" @@ -512,11 +288,12 @@ } }, "@babel/helper-create-regexp-features-plugin": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.8.3.tgz", - "integrity": "sha512-Gcsm1OHCUr9o9TcJln57xhWHtdXbA2pgQ58S0Lxlks0WMGNXuki4+GLfX0p+L2ZkINUGZvfkz8rzoqJQSthI+Q==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.8.6.tgz", + "integrity": "sha512-bPyujWfsHhV/ztUkwGHz/RPV1T1TDEsSZDsN42JPehndA+p1KKTh3npvTadux0ZhCrytx9tvjpWNowKby3tM6A==", "dev": true, "requires": { + "@babel/helper-annotate-as-pure": "^7.8.3", "@babel/helper-regex": "^7.8.3", "regexpu-core": "^4.6.0" } @@ -530,94 +307,6 @@ "@babel/helper-function-name": "^7.8.3", "@babel/types": "^7.8.3", "lodash": "^4.17.13" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", - "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", - "dev": true, - "requires": { - "@babel/highlight": "^7.8.3" - } - }, - "@babel/helper-function-name": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz", - "integrity": "sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.8.3", - "@babel/template": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", - "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/highlight": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz", - "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==", - "dev": true, - "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", - "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", - "dev": true - }, - "@babel/template": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.3.tgz", - "integrity": "sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/parser": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "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" - } - }, - "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" - } - } } }, "@babel/helper-explode-assignable-expression": { @@ -628,167 +317,26 @@ "requires": { "@babel/traverse": "^7.8.3", "@babel/types": "^7.8.3" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", - "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", - "dev": true, - "requires": { - "@babel/highlight": "^7.8.3" - } - }, - "@babel/generator": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.4.tgz", - "integrity": "sha512-PwhclGdRpNAf3IxZb0YVuITPZmmrXz9zf6fH8lT4XbrmfQKr6ryBzhv593P5C6poJRciFCL/eHGW2NuGrgEyxA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3", - "jsesc": "^2.5.1", - "lodash": "^4.17.13", - "source-map": "^0.5.0" - } - }, - "@babel/helper-function-name": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz", - "integrity": "sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.8.3", - "@babel/template": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", - "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz", - "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/highlight": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz", - "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==", - "dev": true, - "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", - "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", - "dev": true - }, - "@babel/template": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.3.tgz", - "integrity": "sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/parser": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/traverse": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.4.tgz", - "integrity": "sha512-NGLJPZwnVEyBPLI+bl9y9aSnxMhsKz42so7ApAv9D+b4vAFPpY013FTS9LdKxcABoIYFU52HcYga1pPlx454mg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.8.4", - "@babel/helper-function-name": "^7.8.3", - "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/parser": "^7.8.4", - "@babel/types": "^7.8.3", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.13" - } - }, - "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" - } - }, - "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" - } - }, - "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 - }, - "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" - } - } } }, "@babel/helper-function-name": { - "version": "7.7.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.7.0.tgz", - "integrity": "sha512-tDsJgMUAP00Ugv8O2aGEua5I2apkaQO7lBGUq1ocwN3G23JE5Dcq0uh3GvFTChPa4b40AWiAsLvCZOA2rdnQ7Q==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz", + "integrity": "sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.7.0", - "@babel/template": "^7.7.0", - "@babel/types": "^7.7.0" + "@babel/helper-get-function-arity": "^7.8.3", + "@babel/template": "^7.8.3", + "@babel/types": "^7.8.3" } }, "@babel/helper-get-function-arity": { - "version": "7.7.0", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.7.0.tgz", - "integrity": "sha512-tLdojOTz4vWcEnHWHCuPN5P85JLZWbm5Fx5ZsMEMPhF3Uoe3O7awrbM2nQ04bDOUToH/2tH/ezKEOR8zEYzqyw==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", + "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", "dev": true, "requires": { - "@babel/types": "^7.7.0" + "@babel/types": "^7.8.3" } }, "@babel/helper-hoist-variables": { @@ -819,94 +367,18 @@ } }, "@babel/helper-module-transforms": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.8.3.tgz", - "integrity": "sha512-C7NG6B7vfBa/pwCOshpMbOYUmrYQDfCpVL/JCRu0ek8B5p8kue1+BCXpg2vOYs7w5ACB9GTOBYQ5U6NwrMg+3Q==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.8.6.tgz", + "integrity": "sha512-RDnGJSR5EFBJjG3deY0NiL0K9TO8SXxS9n/MPsbPK/s9LbQymuLNtlzvDiNS7IpecuL45cMeLVkA+HfmlrnkRg==", "dev": true, "requires": { "@babel/helper-module-imports": "^7.8.3", + "@babel/helper-replace-supers": "^7.8.6", "@babel/helper-simple-access": "^7.8.3", "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/template": "^7.8.3", - "@babel/types": "^7.8.3", + "@babel/template": "^7.8.6", + "@babel/types": "^7.8.6", "lodash": "^4.17.13" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", - "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", - "dev": true, - "requires": { - "@babel/highlight": "^7.8.3" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz", - "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/highlight": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz", - "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==", - "dev": true, - "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", - "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", - "dev": true - }, - "@babel/template": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.3.tgz", - "integrity": "sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/parser": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "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" - } - }, - "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" - } - } } }, "@babel/helper-optimise-call-expression": { @@ -943,541 +415,50 @@ "@babel/helper-wrap-function": "^7.8.3", "@babel/template": "^7.8.3", "@babel/traverse": "^7.8.3", - "@babel/types": "^7.8.3" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", - "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", - "dev": true, - "requires": { - "@babel/highlight": "^7.8.3" - } - }, - "@babel/generator": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.4.tgz", - "integrity": "sha512-PwhclGdRpNAf3IxZb0YVuITPZmmrXz9zf6fH8lT4XbrmfQKr6ryBzhv593P5C6poJRciFCL/eHGW2NuGrgEyxA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3", - "jsesc": "^2.5.1", - "lodash": "^4.17.13", - "source-map": "^0.5.0" - } - }, - "@babel/helper-function-name": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz", - "integrity": "sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.8.3", - "@babel/template": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", - "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz", - "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/highlight": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz", - "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==", - "dev": true, - "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", - "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", - "dev": true - }, - "@babel/template": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.3.tgz", - "integrity": "sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/parser": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/traverse": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.4.tgz", - "integrity": "sha512-NGLJPZwnVEyBPLI+bl9y9aSnxMhsKz42so7ApAv9D+b4vAFPpY013FTS9LdKxcABoIYFU52HcYga1pPlx454mg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.8.4", - "@babel/helper-function-name": "^7.8.3", - "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/parser": "^7.8.4", - "@babel/types": "^7.8.3", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.13" - } - }, - "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" - } - }, - "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" - } - }, - "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 - }, - "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" - } - } - } - }, - "@babel/helper-replace-supers": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.8.3.tgz", - "integrity": "sha512-xOUssL6ho41U81etpLoT2RTdvdus4VfHamCuAm4AHxGr+0it5fnwoVdwUJ7GFEqCsQYzJUhcbsN9wB9apcYKFA==", - "dev": true, - "requires": { - "@babel/helper-member-expression-to-functions": "^7.8.3", - "@babel/helper-optimise-call-expression": "^7.8.3", - "@babel/traverse": "^7.8.3", - "@babel/types": "^7.8.3" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", - "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", - "dev": true, - "requires": { - "@babel/highlight": "^7.8.3" - } - }, - "@babel/generator": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.4.tgz", - "integrity": "sha512-PwhclGdRpNAf3IxZb0YVuITPZmmrXz9zf6fH8lT4XbrmfQKr6ryBzhv593P5C6poJRciFCL/eHGW2NuGrgEyxA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3", - "jsesc": "^2.5.1", - "lodash": "^4.17.13", - "source-map": "^0.5.0" - } - }, - "@babel/helper-function-name": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz", - "integrity": "sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.8.3", - "@babel/template": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", - "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz", - "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/highlight": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz", - "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==", - "dev": true, - "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", - "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", - "dev": true - }, - "@babel/template": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.3.tgz", - "integrity": "sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/parser": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/traverse": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.4.tgz", - "integrity": "sha512-NGLJPZwnVEyBPLI+bl9y9aSnxMhsKz42so7ApAv9D+b4vAFPpY013FTS9LdKxcABoIYFU52HcYga1pPlx454mg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.8.4", - "@babel/helper-function-name": "^7.8.3", - "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/parser": "^7.8.4", - "@babel/types": "^7.8.3", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.13" - } - }, - "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" - } - }, - "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" - } - }, - "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 - }, - "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" - } - } - } - }, - "@babel/helper-simple-access": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.8.3.tgz", - "integrity": "sha512-VNGUDjx5cCWg4vvCTR8qQ7YJYZ+HBjxOgXEl7ounz+4Sn7+LMD3CFrCTEU6/qXKbA2nKg21CwhhBzO0RpRbdCw==", - "dev": true, - "requires": { - "@babel/template": "^7.8.3", - "@babel/types": "^7.8.3" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", - "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", - "dev": true, - "requires": { - "@babel/highlight": "^7.8.3" - } - }, - "@babel/highlight": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz", - "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==", - "dev": true, - "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", - "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", - "dev": true - }, - "@babel/template": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.3.tgz", - "integrity": "sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/parser": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "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" - } - }, - "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" - } - } - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.7.0", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.7.0.tgz", - "integrity": "sha512-HgYSI8rH08neWlAH3CcdkFg9qX9YsZysZI5GD8LjhQib/mM0jGOZOVkoUiiV2Hu978fRtjtsGsW6w0pKHUWtqA==", - "dev": true, - "requires": { - "@babel/types": "^7.7.0" - } - }, - "@babel/helper-wrap-function": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.8.3.tgz", - "integrity": "sha512-LACJrbUET9cQDzb6kG7EeD7+7doC3JNvUgTEQOx2qaO1fKlzE/Bf05qs9w1oXQMmXlPO65lC3Tq9S6gZpTErEQ==", - "dev": true, - "requires": { - "@babel/helper-function-name": "^7.8.3", - "@babel/template": "^7.8.3", - "@babel/traverse": "^7.8.3", - "@babel/types": "^7.8.3" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", - "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", - "dev": true, - "requires": { - "@babel/highlight": "^7.8.3" - } - }, - "@babel/generator": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.4.tgz", - "integrity": "sha512-PwhclGdRpNAf3IxZb0YVuITPZmmrXz9zf6fH8lT4XbrmfQKr6ryBzhv593P5C6poJRciFCL/eHGW2NuGrgEyxA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3", - "jsesc": "^2.5.1", - "lodash": "^4.17.13", - "source-map": "^0.5.0" - } - }, - "@babel/helper-function-name": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz", - "integrity": "sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.8.3", - "@babel/template": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", - "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz", - "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/highlight": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz", - "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==", - "dev": true, - "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", - "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", - "dev": true - }, - "@babel/template": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.3.tgz", - "integrity": "sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/parser": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/traverse": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.4.tgz", - "integrity": "sha512-NGLJPZwnVEyBPLI+bl9y9aSnxMhsKz42so7ApAv9D+b4vAFPpY013FTS9LdKxcABoIYFU52HcYga1pPlx454mg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.8.4", - "@babel/helper-function-name": "^7.8.3", - "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/parser": "^7.8.4", - "@babel/types": "^7.8.3", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.13" - } - }, - "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" - } - }, - "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" - } - }, - "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 - }, - "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" - } - } + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-replace-supers": { + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.8.6.tgz", + "integrity": "sha512-PeMArdA4Sv/Wf4zXwBKPqVj7n9UF/xg6slNRtZW84FM7JpE1CbG8B612FyM4cxrf4fMAMGO0kR7voy1ForHHFA==", + "dev": true, + "requires": { + "@babel/helper-member-expression-to-functions": "^7.8.3", + "@babel/helper-optimise-call-expression": "^7.8.3", + "@babel/traverse": "^7.8.6", + "@babel/types": "^7.8.6" + } + }, + "@babel/helper-simple-access": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.8.3.tgz", + "integrity": "sha512-VNGUDjx5cCWg4vvCTR8qQ7YJYZ+HBjxOgXEl7ounz+4Sn7+LMD3CFrCTEU6/qXKbA2nKg21CwhhBzO0RpRbdCw==", + "dev": true, + "requires": { + "@babel/template": "^7.8.3", + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz", + "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", + "dev": true, + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-wrap-function": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.8.3.tgz", + "integrity": "sha512-LACJrbUET9cQDzb6kG7EeD7+7doC3JNvUgTEQOx2qaO1fKlzE/Bf05qs9w1oXQMmXlPO65lC3Tq9S6gZpTErEQ==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.8.3", + "@babel/template": "^7.8.3", + "@babel/traverse": "^7.8.3", + "@babel/types": "^7.8.3" } }, "@babel/helpers": { @@ -1489,147 +470,6 @@ "@babel/template": "^7.8.3", "@babel/traverse": "^7.8.4", "@babel/types": "^7.8.3" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", - "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", - "dev": true, - "requires": { - "@babel/highlight": "^7.8.3" - } - }, - "@babel/generator": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.4.tgz", - "integrity": "sha512-PwhclGdRpNAf3IxZb0YVuITPZmmrXz9zf6fH8lT4XbrmfQKr6ryBzhv593P5C6poJRciFCL/eHGW2NuGrgEyxA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3", - "jsesc": "^2.5.1", - "lodash": "^4.17.13", - "source-map": "^0.5.0" - } - }, - "@babel/helper-function-name": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz", - "integrity": "sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.8.3", - "@babel/template": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", - "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz", - "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/highlight": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz", - "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==", - "dev": true, - "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", - "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", - "dev": true - }, - "@babel/template": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.3.tgz", - "integrity": "sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/parser": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/traverse": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.4.tgz", - "integrity": "sha512-NGLJPZwnVEyBPLI+bl9y9aSnxMhsKz42so7ApAv9D+b4vAFPpY013FTS9LdKxcABoIYFU52HcYga1pPlx454mg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.8.4", - "@babel/helper-function-name": "^7.8.3", - "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/parser": "^7.8.4", - "@babel/types": "^7.8.3", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.13" - } - }, - "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" - } - }, - "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" - } - }, - "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 - }, - "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" - } - } } }, "@babel/highlight": { @@ -1675,9 +515,9 @@ } }, "@babel/parser": { - "version": "7.7.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.7.0.tgz", - "integrity": "sha512-GqL+Z0d7B7ADlQBMXlJgvXEbtt5qlqd1YQ5fr12hTSfh7O/vgrEIvJxU2e7aSVrEUn75zTZ6Nd0s8tthrlZnrQ==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.6.tgz", + "integrity": "sha512-trGNYSfwq5s0SgM1BMEB8hX3NDmO7EP2wsDGDexiaKMB92BaRpS+qZfpkMqUBhcsOTBwNy9B/jieo4ad/t/z2g==", "dev": true }, "@babel/plugin-proposal-async-generator-functions": { @@ -1846,152 +686,55 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.8.3.tgz", "integrity": "sha512-0MRF+KC8EqH4dbuITCWwPSzsyO3HIWWlm30v8BbbpOrS1B++isGxPnnuq/IZvOX5J2D/p7DQalQm+/2PnlKGxg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-async-to-generator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.8.3.tgz", - "integrity": "sha512-imt9tFLD9ogt56Dd5CI/6XgpukMwd/fLGSrix2httihVe7LOGVPhyhMh1BU5kDM7iHD08i8uUtmV2sWaBFlHVQ==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/helper-remap-async-to-generator": "^7.8.3" - } - }, - "@babel/plugin-transform-block-scoped-functions": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.8.3.tgz", - "integrity": "sha512-vo4F2OewqjbB1+yaJ7k2EJFHlTP3jR634Z9Cj9itpqNjuLXvhlVxgnjsHsdRgASR8xYDrx6onw4vW5H6We0Jmg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-block-scoping": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.8.3.tgz", - "integrity": "sha512-pGnYfm7RNRgYRi7bids5bHluENHqJhrV4bCZRwc5GamaWIIs07N4rZECcmJL6ZClwjDz1GbdMZFtPs27hTB06w==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "lodash": "^4.17.13" - } - }, - "@babel/plugin-transform-classes": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.8.3.tgz", - "integrity": "sha512-SjT0cwFJ+7Rbr1vQsvphAHwUHvSUPmMjMU/0P59G8U2HLFqSa082JO7zkbDNWs9kH/IUqpHI6xWNesGf8haF1w==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.8.3", - "@babel/helper-define-map": "^7.8.3", - "@babel/helper-function-name": "^7.8.3", - "@babel/helper-optimise-call-expression": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/helper-replace-supers": "^7.8.3", - "@babel/helper-split-export-declaration": "^7.8.3", - "globals": "^11.1.0" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", - "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", - "dev": true, - "requires": { - "@babel/highlight": "^7.8.3" - } - }, - "@babel/helper-function-name": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz", - "integrity": "sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.8.3", - "@babel/template": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", - "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz", - "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/highlight": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz", - "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==", - "dev": true, - "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", - "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", - "dev": true - }, - "@babel/template": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.3.tgz", - "integrity": "sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/parser": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "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" - } - }, - "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" - } - } + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-transform-async-to-generator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.8.3.tgz", + "integrity": "sha512-imt9tFLD9ogt56Dd5CI/6XgpukMwd/fLGSrix2httihVe7LOGVPhyhMh1BU5kDM7iHD08i8uUtmV2sWaBFlHVQ==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.8.3", + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/helper-remap-async-to-generator": "^7.8.3" + } + }, + "@babel/plugin-transform-block-scoped-functions": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.8.3.tgz", + "integrity": "sha512-vo4F2OewqjbB1+yaJ7k2EJFHlTP3jR634Z9Cj9itpqNjuLXvhlVxgnjsHsdRgASR8xYDrx6onw4vW5H6We0Jmg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-transform-block-scoping": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.8.3.tgz", + "integrity": "sha512-pGnYfm7RNRgYRi7bids5bHluENHqJhrV4bCZRwc5GamaWIIs07N4rZECcmJL6ZClwjDz1GbdMZFtPs27hTB06w==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.3", + "lodash": "^4.17.13" + } + }, + "@babel/plugin-transform-classes": { + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.8.6.tgz", + "integrity": "sha512-k9r8qRay/R6v5aWZkrEclEhKO6mc1CCQr2dLsVHBmOQiMpN6I2bpjX3vgnldUWeEI1GHVNByULVxZ4BdP4Hmdg==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.8.3", + "@babel/helper-define-map": "^7.8.3", + "@babel/helper-function-name": "^7.8.3", + "@babel/helper-optimise-call-expression": "^7.8.3", + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/helper-replace-supers": "^7.8.6", + "@babel/helper-split-export-declaration": "^7.8.3", + "globals": "^11.1.0" } }, "@babel/plugin-transform-computed-properties": { @@ -2042,9 +785,9 @@ } }, "@babel/plugin-transform-for-of": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.8.4.tgz", - "integrity": "sha512-iAXNlOWvcYUYoV8YIxwS7TxGRJcxyl8eQCfT+A5j8sKUzRFvJdcyjp97jL2IghWSRDaL2PU2O2tX8Cu9dTBq5A==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.8.6.tgz", + "integrity": "sha512-M0pw4/1/KI5WAxPsdcUL/w2LJ7o89YHN3yLkzNjg7Yl15GlVGgzHyCU+FMeAxevHGsLVmUqbirlUIKTafPmzdw==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.8.3" @@ -2058,94 +801,6 @@ "requires": { "@babel/helper-function-name": "^7.8.3", "@babel/helper-plugin-utils": "^7.8.3" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", - "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", - "dev": true, - "requires": { - "@babel/highlight": "^7.8.3" - } - }, - "@babel/helper-function-name": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz", - "integrity": "sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.8.3", - "@babel/template": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", - "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/highlight": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz", - "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==", - "dev": true, - "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", - "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", - "dev": true - }, - "@babel/template": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.3.tgz", - "integrity": "sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/parser": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "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" - } - }, - "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" - } - } } }, "@babel/plugin-transform-literals": { @@ -2248,17 +903,6 @@ "@babel/helper-call-delegate": "^7.8.3", "@babel/helper-get-function-arity": "^7.8.3", "@babel/helper-plugin-utils": "^7.8.3" - }, - "dependencies": { - "@babel/helper-get-function-arity": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", - "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3" - } - } } }, "@babel/plugin-transform-property-literals": { @@ -2396,13 +1040,13 @@ } }, "@babel/preset-env": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.8.4.tgz", - "integrity": "sha512-HihCgpr45AnSOHRbS5cWNTINs0TwaR8BS8xIIH+QwiW8cKL0llV91njQMpeMReEPVs+1Ao0x3RLEBLtt1hOq4w==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.8.6.tgz", + "integrity": "sha512-M5u8llV9DIVXBFB/ArIpqJuvXpO+ymxcJ6e8ZAmzeK3sQeBNOD1y+rHvHCGG4TlEmsNpIrdecsHGHT8ZCoOSJg==", "dev": true, "requires": { - "@babel/compat-data": "^7.8.4", - "@babel/helper-compilation-targets": "^7.8.4", + "@babel/compat-data": "^7.8.6", + "@babel/helper-compilation-targets": "^7.8.6", "@babel/helper-module-imports": "^7.8.3", "@babel/helper-plugin-utils": "^7.8.3", "@babel/plugin-proposal-async-generator-functions": "^7.8.3", @@ -2425,13 +1069,13 @@ "@babel/plugin-transform-async-to-generator": "^7.8.3", "@babel/plugin-transform-block-scoped-functions": "^7.8.3", "@babel/plugin-transform-block-scoping": "^7.8.3", - "@babel/plugin-transform-classes": "^7.8.3", + "@babel/plugin-transform-classes": "^7.8.6", "@babel/plugin-transform-computed-properties": "^7.8.3", "@babel/plugin-transform-destructuring": "^7.8.3", "@babel/plugin-transform-dotall-regex": "^7.8.3", "@babel/plugin-transform-duplicate-keys": "^7.8.3", "@babel/plugin-transform-exponentiation-operator": "^7.8.3", - "@babel/plugin-transform-for-of": "^7.8.4", + "@babel/plugin-transform-for-of": "^7.8.6", "@babel/plugin-transform-function-name": "^7.8.3", "@babel/plugin-transform-literals": "^7.8.3", "@babel/plugin-transform-member-expression-literals": "^7.8.3", @@ -2452,7 +1096,7 @@ "@babel/plugin-transform-template-literals": "^7.8.3", "@babel/plugin-transform-typeof-symbol": "^7.8.4", "@babel/plugin-transform-unicode-regex": "^7.8.3", - "@babel/types": "^7.8.3", + "@babel/types": "^7.8.6", "browserslist": "^4.8.5", "core-js-compat": "^3.6.2", "invariant": "^2.2.2", @@ -2461,32 +1105,32 @@ }, "dependencies": { "browserslist": { - "version": "4.8.6", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.8.6.tgz", - "integrity": "sha512-ZHao85gf0eZ0ESxLfCp73GG9O/VTytYDIkIiZDlURppLTI9wErSM/5yAKEq6rcUdxBLjMELmrYUJGg5sxGKMHg==", + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.9.0.tgz", + "integrity": "sha512-seffIXhwgB84+OCeT/aMjpZnsAsYDiMSC+CEs3UkF8iU64BZGYcu+TZYs/IBpo4nRi0vJywUJWYdbTsOhFTweg==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001023", - "electron-to-chromium": "^1.3.341", - "node-releases": "^1.1.47" + "caniuse-lite": "^1.0.30001030", + "electron-to-chromium": "^1.3.361", + "node-releases": "^1.1.50" } }, "caniuse-lite": { - "version": "1.0.30001025", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001025.tgz", - "integrity": "sha512-SKyFdHYfXUZf5V85+PJgLYyit27q4wgvZuf8QTOk1osbypcROihMBlx9GRar2/pIcKH2r4OehdlBr9x6PXetAQ==", + "version": "1.0.30001030", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001030.tgz", + "integrity": "sha512-QGK0W4Ft/Ac+zTjEiRJfwDNATvS3fodDczBXrH42784kcfqcDKpEPfN08N0HQjrAp8He/Jw8QiSS9QRn7XAbUw==", "dev": true }, "electron-to-chromium": { - "version": "1.3.345", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.345.tgz", - "integrity": "sha512-f8nx53+Z9Y+SPWGg3YdHrbYYfIJAtbUjpFfW4X1RwTZ94iUG7geg9tV8HqzAXX7XTNgyWgAFvce4yce8ZKxKmg==", + "version": "1.3.363", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.363.tgz", + "integrity": "sha512-4w19wPBkeunBjOA53lNFT36IdOD3Tk1OoIDtTX+VToJUUDX42QfuhtsNKXv25wmSnoBOExM3kTbj7/WDNBwHuQ==", "dev": true }, "node-releases": { - "version": "1.1.47", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.47.tgz", - "integrity": "sha512-k4xjVPx5FpwBUj0Gw7uvFOTF4Ep8Hok1I6qjwL3pLfwe7Y0REQSAqOwwv9TWBCUtMHxcXfY4PgRLRozcChvTcA==", + "version": "1.1.50", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.50.tgz", + "integrity": "sha512-lgAmPv9eYZ0bGwUYAKlr8MG6K4CvWliWqnkcT2P8mMAgVrH3lqfBPorFlxiG1pHQnqmavJZ9vbMXUTNyMLbrgQ==", "dev": true, "requires": { "semver": "^6.3.0" @@ -2524,33 +1168,124 @@ } }, "@babel/template": { - "version": "7.7.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.7.0.tgz", - "integrity": "sha512-OKcwSYOW1mhWbnTBgQY5lvg1Fxg+VyfQGjcBduZFljfc044J5iDlnDSfhQ867O17XHiSCxYHUxHg2b7ryitbUQ==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.6.tgz", + "integrity": "sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg==", "dev": true, "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.7.0", - "@babel/types": "^7.7.0" + "@babel/code-frame": "^7.8.3", + "@babel/parser": "^7.8.6", + "@babel/types": "^7.8.6" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", + "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", + "dev": true, + "requires": { + "@babel/highlight": "^7.8.3" + } + }, + "@babel/highlight": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz", + "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==", + "dev": true, + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.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, + "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" + } + }, + "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" + } + } } }, "@babel/traverse": { - "version": "7.7.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.7.0.tgz", - "integrity": "sha512-ea/3wRZc//e/uwCpuBX2itrhI0U9l7+FsrKWyKGNyvWbuMcCG7ATKY2VI4wlg2b2TA39HHwIxnvmXvtiKsyn7w==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.6.tgz", + "integrity": "sha512-2B8l0db/DPi8iinITKuo7cbPznLCEk0kCxDoB9/N6gGNg/gxOXiR/IcymAFPiBwk5w6TtQ27w4wpElgp9btR9A==", "dev": true, "requires": { - "@babel/code-frame": "^7.5.5", - "@babel/generator": "^7.7.0", - "@babel/helper-function-name": "^7.7.0", - "@babel/helper-split-export-declaration": "^7.7.0", - "@babel/parser": "^7.7.0", - "@babel/types": "^7.7.0", + "@babel/code-frame": "^7.8.3", + "@babel/generator": "^7.8.6", + "@babel/helper-function-name": "^7.8.3", + "@babel/helper-split-export-declaration": "^7.8.3", + "@babel/parser": "^7.8.6", + "@babel/types": "^7.8.6", "debug": "^4.1.0", "globals": "^11.1.0", "lodash": "^4.17.13" }, "dependencies": { + "@babel/code-frame": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", + "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", + "dev": true, + "requires": { + "@babel/highlight": "^7.8.3" + } + }, + "@babel/highlight": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz", + "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==", + "dev": true, + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.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, + "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", @@ -2565,13 +1300,22 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "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" + } } } }, "@babel/types": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", - "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.6.tgz", + "integrity": "sha512-wqz7pgWMIrht3gquyEFPVXeXCti72Rm8ep9b5tQKz9Yg9LzJA3HxosF1SB3Kc81KD1A3XBkkVYtJvCKS2Z/QrA==", "dev": true, "requires": { "esutils": "^2.0.2", @@ -3951,15 +2695,15 @@ } }, "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==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz", + "integrity": "sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.0.0", - "@babel/traverse": "^7.0.0", - "@babel/types": "^7.0.0", + "@babel/parser": "^7.7.0", + "@babel/traverse": "^7.7.0", + "@babel/types": "^7.7.0", "eslint-visitor-keys": "^1.0.0", "resolve": "^1.12.0" } @@ -4996,32 +3740,32 @@ }, "dependencies": { "browserslist": { - "version": "4.8.6", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.8.6.tgz", - "integrity": "sha512-ZHao85gf0eZ0ESxLfCp73GG9O/VTytYDIkIiZDlURppLTI9wErSM/5yAKEq6rcUdxBLjMELmrYUJGg5sxGKMHg==", + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.9.0.tgz", + "integrity": "sha512-seffIXhwgB84+OCeT/aMjpZnsAsYDiMSC+CEs3UkF8iU64BZGYcu+TZYs/IBpo4nRi0vJywUJWYdbTsOhFTweg==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001023", - "electron-to-chromium": "^1.3.341", - "node-releases": "^1.1.47" + "caniuse-lite": "^1.0.30001030", + "electron-to-chromium": "^1.3.361", + "node-releases": "^1.1.50" } }, "caniuse-lite": { - "version": "1.0.30001025", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001025.tgz", - "integrity": "sha512-SKyFdHYfXUZf5V85+PJgLYyit27q4wgvZuf8QTOk1osbypcROihMBlx9GRar2/pIcKH2r4OehdlBr9x6PXetAQ==", + "version": "1.0.30001030", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001030.tgz", + "integrity": "sha512-QGK0W4Ft/Ac+zTjEiRJfwDNATvS3fodDczBXrH42784kcfqcDKpEPfN08N0HQjrAp8He/Jw8QiSS9QRn7XAbUw==", "dev": true }, "electron-to-chromium": { - "version": "1.3.345", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.345.tgz", - "integrity": "sha512-f8nx53+Z9Y+SPWGg3YdHrbYYfIJAtbUjpFfW4X1RwTZ94iUG7geg9tV8HqzAXX7XTNgyWgAFvce4yce8ZKxKmg==", + "version": "1.3.363", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.363.tgz", + "integrity": "sha512-4w19wPBkeunBjOA53lNFT36IdOD3Tk1OoIDtTX+VToJUUDX42QfuhtsNKXv25wmSnoBOExM3kTbj7/WDNBwHuQ==", "dev": true }, "node-releases": { - "version": "1.1.47", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.47.tgz", - "integrity": "sha512-k4xjVPx5FpwBUj0Gw7uvFOTF4Ep8Hok1I6qjwL3pLfwe7Y0REQSAqOwwv9TWBCUtMHxcXfY4PgRLRozcChvTcA==", + "version": "1.1.50", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.50.tgz", + "integrity": "sha512-lgAmPv9eYZ0bGwUYAKlr8MG6K4CvWliWqnkcT2P8mMAgVrH3lqfBPorFlxiG1pHQnqmavJZ9vbMXUTNyMLbrgQ==", "dev": true, "requires": { "semver": "^6.3.0" @@ -6856,9 +5600,9 @@ } }, "eslint-plugin-react-hooks": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-2.4.0.tgz", - "integrity": "sha512-bH5DOCP6WpuOqNaux2BlaDCrSgv8s5BitP90bTgtZ1ZsRn2bdIfeMDY5F2RnJVnyKDy6KRQRDbipPLZ1y77QtQ==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-2.5.0.tgz", + "integrity": "sha512-bzvdX47Jx847bgAYf0FPX3u1oxU+mKU8tqrpj4UX9A96SbAmj/HVEefEy6rJUog5u8QIlOPTKZcBpGn5kkKfAQ==", "dev": true }, "eslint-scope": { @@ -10160,7 +8904,25 @@ "prepend-http": "^1.0.0", "query-string": "^4.1.0", "sort-keys": "^1.0.0" + }, + "dependencies": { + "query-string": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz", + "integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=", + "dev": true, + "requires": { + "object-assign": "^4.1.0", + "strict-uri-encode": "^1.0.0" + } + } } + }, + "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=", + "dev": true } } }, @@ -14064,13 +12826,13 @@ "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" }, "query-string": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz", - "integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=", - "dev": true, + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.11.0.tgz", + "integrity": "sha512-jS+me8X3OEGFTsF6kF+vUUMFG/d3WUCvD7bHhfZP5784nOq1pjj8yau/u86nfOncmcN6ZkSWKWkKAvv/MGxzLA==", "requires": { - "object-assign": "^4.1.0", - "strict-uri-encode": "^1.0.0" + "decode-uri-component": "^0.2.0", + "split-on-first": "^1.0.0", + "strict-uri-encode": "^2.0.0" } }, "querystring": { @@ -14137,9 +12899,9 @@ } }, "react": { - "version": "16.12.0", - "resolved": "https://registry.npmjs.org/react/-/react-16.12.0.tgz", - "integrity": "sha512-fglqy3k5E+81pA8s+7K0/T3DBCF0ZDOher1elBFzF7O6arXJgzyu/FW+COxFvAWXJoJN9KIZbT2LXlukwphYTA==", + "version": "16.13.0", + "resolved": "https://registry.npmjs.org/react/-/react-16.13.0.tgz", + "integrity": "sha512-TSavZz2iSLkq5/oiE7gnFzmURKZMltmi193rm5HEoUDAXpzT9Kzw6oNZnGoai/4+fUnm7FqS5dwgUL34TujcWQ==", "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", @@ -14171,14 +12933,14 @@ } }, "react-dom": { - "version": "16.12.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.12.0.tgz", - "integrity": "sha512-LMxFfAGrcS3kETtQaCkTKjMiifahaMySFDn71fZUNpPHZQEzmk/GiAeIT8JSOrHB23fnuCOMruL2a8NYlw+8Gw==", + "version": "16.13.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.13.0.tgz", + "integrity": "sha512-y09d2c4cG220DzdlFkPTnVvGTszVvNpC73v+AaLGLHbkpy3SSgvYq8x0rNwPJ/Rk/CicTNgk0hbHNw1gMEZAXg==", "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", "prop-types": "^15.6.2", - "scheduler": "^0.18.0" + "scheduler": "^0.19.0" } }, "react-is": { @@ -14498,9 +13260,9 @@ "dev": true }, "regjsparser": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.2.tgz", - "integrity": "sha512-E9ghzUtoLwDekPT0DYCp+c4h+bvuUpe6rRHCTYn6eGoqj1LgKXxT6I0Il4WbjhQkOghzi/V+y03bPKvbllL93Q==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.3.tgz", + "integrity": "sha512-8uZvYbnfAtEm9Ab8NTb3hdLwL4g/LQzEYP7Xs27T96abJCCE2d6r3cPZPQEsLKy0vRSGVNG+/zVGtLr86HQduA==", "dev": true, "requires": { "jsesc": "~0.5.0" @@ -15156,9 +13918,9 @@ "dev": true }, "scheduler": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.18.0.tgz", - "integrity": "sha512-agTSHR1Nbfi6ulI0kYNK0203joW2Y5W4po4l+v03tOoiJKpTBbxpNhWDvqc/4IcOw+KLmSiQLTasZ4cab2/UWQ==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.0.tgz", + "integrity": "sha512-xowbVaTPe9r7y7RUejcK73/j8tt2jfiyTednOvHbA8JoClvMYCp+r8QegLwK/n8zWQAtZb1fFnER4XLBZXrCxA==", "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1" @@ -15850,6 +14612,11 @@ } } }, + "split-on-first": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", + "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==" + }, "split-string": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", @@ -15968,10 +14735,9 @@ "dev": true }, "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=", - "dev": true + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", + "integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY=" }, "string-width": { "version": "1.0.2", diff --git a/package.json b/package.json index a9015aa1b..9b9de59a5 100644 --- a/package.json +++ b/package.json @@ -19,11 +19,12 @@ "@stripe/stripe-js": "^1.0.2", "font-awesome": "^4.7.0", "json-stringify-pretty-compact": "^2.0.0", - "react": "^16.12.0", + "query-string": "^6.11.0", + "react": "^16.13.0", "react-alert": "^6.0.0", "react-card-flip": "^1.0.10", "react-csv-reader": "^2.0.2", - "react-dom": "^16.12.0", + "react-dom": "^16.13.0", "react-json-editor-ajrm": "^2.5.9", "react-router": "^5.1.2", "react-router-dom": "^5.1.2", @@ -36,14 +37,14 @@ }, "devDependencies": { "@babel/cli": "^7.8.4", - "@babel/core": "^7.8.4", + "@babel/core": "^7.8.6", "@babel/plugin-proposal-optional-chaining": "^7.8.3", "@babel/polyfill": "^7.8.3", - "@babel/preset-env": "^7.8.4", + "@babel/preset-env": "^7.8.6", "@babel/preset-react": "^7.8.3", "@babel/runtime": "^7.8.4", - "@babel/types": "^7.8.3", - "babel-eslint": "^10.0.3", + "@babel/types": "^7.8.6", + "babel-eslint": "^10.1.0", "babel-loader": "^8.0.6", "babel-plugin-transform-imports": "^2.0.0", "clean-webpack-plugin": "^3.0.0", @@ -55,7 +56,7 @@ "eslint-plugin-import": "^2.20.1", "eslint-plugin-jsx-a11y": "^6.2.3", "eslint-plugin-react": "^7.18.3", - "eslint-plugin-react-hooks": "^2.4.0", + "eslint-plugin-react-hooks": "^2.5.0", "file-loader": "^5.1.0", "html-loader": "^0.5.5", "html-webpack-inline-source-plugin": "0.0.10", diff --git a/src/api/lms/getProducts.js b/src/api/lms/getProducts.js index 03c49bde2..224c027c1 100644 --- a/src/api/lms/getProducts.js +++ b/src/api/lms/getProducts.js @@ -1,14 +1,46 @@ import queryLMS from '../queryLMS'; +const buildRadioSelectProductOptions = ({ id, amount_decimal, interval, amount, metadata: { instance_ram } }) => { + return { + price: amount_decimal !== '0' ? (amount_decimal / 100).toFixed(2) : 'FREE', + ram: instance_ram, + interval, + label: `${instance_ram} RAM | ${amount_decimal !== '0' ? `${amount}/${interval}` : 'FREE'}`, + value: id, + }; +}; + +const buildRadioSelectStorageOptions = (size, { tiers, interval }) => { + const pricingTier = tiers.find((p) => (p.up_to && size <= p.up_to) || !p.up_to); + const price = (size * (pricingTier.unit_amount / 100)).toFixed(2); + return { + price: pricingTier.unit_amount ? (size * (pricingTier.unit_amount / 100)).toFixed(2) : 'FREE', + disk_space: size === 1000 ? '1TB' : `${size}GB`, + interval, + label: `${size === 1000 ? '1TB' : `${size}GB`} Disk Space | ${pricingTier.unit_amount ? `$${price}/${interval}` : 'FREE'}`, + value: size, + }; +}; + export default async ({ auth }) => { const newProducts = await queryLMS({ endpoint: 'getProducts', method: 'POST', auth, }); + + const localComputeOptions = newProducts.body.find((p) => p.name === 'HarperDB Local Annual'); + const cloudComputeOptions = newProducts.body.find((p) => p.name === 'HarperDB Cloud Beta'); + const cloudStoragePlans = newProducts.body.find((p) => p.name === 'HarperDB Cloud Storage'); + const cloudStorageOptions = [10, 100, 250, 500, 1000]; + + const cloudStorage = cloudStorageOptions.map((size) => buildRadioSelectStorageOptions(size, cloudStoragePlans.plans[0])); + const cloudCompute = cloudComputeOptions.plans.map((p) => buildRadioSelectProductOptions(p)); + const localCompute = localComputeOptions.plans.map((p) => buildRadioSelectProductOptions(p)); + return { - raw: newProducts, - local: newProducts.map((p) => ({ label: `${p.instance_ram}GB RAM | ${p.instance_disk_space_gigs}GB Disk Space | ${p.local_price_annual ? `$${p.local_price_annual}/yr` : 'FREE'}`, value: p.stripe_product_id })), - cloud: newProducts.map((p) => ({ label: `${p.instance_ram}GB RAM | ${p.instance_disk_space_gigs}GB Disk Space | ${p.cloud_price_monthly ? `$${p.cloud_price_monthly}/mo` : 'FREE'}`, value: p.stripe_product_id })), + cloudStorage, + cloudCompute, + localCompute, }; }; diff --git a/src/api/lms/updateInstance.js b/src/api/lms/updateInstance.js index 554e615c4..04a327802 100644 --- a/src/api/lms/updateInstance.js +++ b/src/api/lms/updateInstance.js @@ -1,8 +1,8 @@ import queryLMS from '../queryLMS'; -export default async ({ auth, instance_name, stripe_product_id, instance_id, customer_id }) => queryLMS({ +export default async ({ auth, instance_name, stripe_product_id, instance_id, customer_id, storage_qty_gb }) => queryLMS({ endpoint: 'updateInstance', method: 'POST', - payload: { stripe_product_id, instance_id, customer_id, instance_name }, + payload: { stripe_product_id, instance_id, customer_id, instance_name, storage_qty_gb }, auth, }); diff --git a/src/assets/styles/_base.scss b/src/assets/styles/_base.scss index d19775b9a..71b6b770e 100644 --- a/src/assets/styles/_base.scss +++ b/src/assets/styles/_base.scss @@ -60,7 +60,7 @@ body { #app-subnav { width: 100%; min-width: $min-width; - background: $light-grey-overlay !important; + background: $dark-grey-overlay !important; padding: 5px 2rem; position: fixed; z-index: 98 !important; @@ -68,7 +68,7 @@ body { top: 60px !important; text-align: center; transition: top 250ms; - box-shadow: inset 0 10px 5px -10px $grey-overlay, inset 0 -10px 5px -10px $grey-overlay; + box-shadow: inset 0 10px 5px -10px $color-black, inset 0 -10px 5px -10px $color-black; .navbar-nav { width: 100%; diff --git a/src/assets/styles/_button.scss b/src/assets/styles/_button.scss index af889edaa..5afb39377 100644 --- a/src/assets/styles/_button.scss +++ b/src/assets/styles/_button.scss @@ -26,25 +26,25 @@ &.btn-grey { color: $color-white !important; - border: 0 !important; + border-color: $grey-overlay !important; background-color: $grey-overlay !important; } &.btn-black { color: $color-white !important; - border: 0 !important; + border-color: $color-black !important; background-color: $color-black !important; } &.btn-purple { color: $color-white !important; - border: 0 !important; + border-color: $color-purple !important; background-color: $color-purple !important; } &.btn-darkpurple { color: $color-white !important; - border: 0 !important; + border-color: $color-darkpurple !important; background-color: $color-darkpurple !important; } diff --git a/src/assets/styles/_card.scss b/src/assets/styles/_card.scss index 2920398a2..1fad0e6de 100644 --- a/src/assets/styles/_card.scss +++ b/src/assets/styles/_card.scss @@ -15,7 +15,7 @@ .scrollable { overflow-x: hidden; overflow-y: auto; - height: 75px; + height: 77px; } } @@ -51,4 +51,12 @@ } } } + + &.no-shadow { + box-shadow: none; + } + + &.no-background { + background: none !important; + } } diff --git a/src/assets/styles/_forms.scss b/src/assets/styles/_forms.scss index f7d09bdf9..a04ce56a5 100644 --- a/src/assets/styles/_forms.scss +++ b/src/assets/styles/_forms.scss @@ -13,7 +13,7 @@ input:-webkit-autofill:active { font-weight: $font-weight-light; font-size: $font-size-root; outline: none !important; - border-radius: 4px; + border-radius: 4px !important; box-shadow: none !important; -webkit-box-shadow: 0 0 0 1000px $light-white-overlay inset; transition: none; @@ -124,6 +124,7 @@ textarea { .stripe-input-holder { padding: 8px 12px 0; text-align: left; + height: 36px; } .nio-check-mark::before { @@ -165,16 +166,27 @@ textarea { .fieldset { border-top: 1px solid $lighter-grey-overlay; padding: 5px 0; - max-height: 129px; + max-height: 131px; margin-bottom: 25px; overflow-x: hidden; overflow-y: auto; + &.full-height { + max-height: none; + } + &:last-child { margin-bottom: 0; } } +#__lpform_input_idx_0, +#__lpform_input_idx_0_numspan, +#__lpform_input_idx_1, +#__lpform_input_idx_1_numspan { + display: none; +} + diff --git a/src/components/account/billing/cardEdit.js b/src/components/account/billing/cardEdit.js index dbc5af6ac..8c1dc427f 100644 --- a/src/components/account/billing/cardEdit.js +++ b/src/components/account/billing/cardEdit.js @@ -2,9 +2,11 @@ import React, { useState } from 'react'; import { Button, Row, Col, Input } from '@nio/ui-kit'; import { CardNumberElement, CardExpiryElement, CardCvcElement, useElements, useStripe } from '@stripe/react-stripe-js'; import useAsyncEffect from 'use-async-effect'; +import { useLocation, useHistory } from 'react-router-dom'; +import queryString from 'query-string' import cardOptions from '../../../util/stripe/cardOptions'; -import useLMS from '../../../state/lmsData'; +import useLMS from '../../../state/stores/lmsData'; import defaultLMSData from '../../../state/defaults/defaultLMSData'; import addPaymentMethod from '../../../api/lms/addPaymentMethod'; import getCustomer from '../../../api/lms/getCustomer'; @@ -13,27 +15,20 @@ export default ({ setEditingCard, customerCard }) => { const [lmsData, setLMSData] = useLMS(defaultLMSData); const [postalCode, setPostalCode] = useState(false); const [cardSubmitted, setCardSubmitted] = useState(false); - const stripe = useStripe(); - const elements = useElements(); - const [error, setError] = useState(null); const [cardComplete, setCardComplete] = useState(false); const [processing, setProcessing] = useState(false); + const stripe = useStripe(); + const elements = useElements(); + const location = useLocation(); + const history = useHistory(); + const { returnURL } = queryString.parse(location.search); + useAsyncEffect(async () => { if (cardSubmitted && stripe && elements) { if (cardComplete) setProcessing(true); - - const newCardObject = { - type: 'card', - card: elements.getElement(CardNumberElement), - billing_details: { - address: { - postal_code: postalCode, - }, - }, - }; - + const newCardObject = { type: 'card', card: elements.getElement(CardNumberElement), billing_details: { address: { postal_code: postalCode } } }; const payload = await stripe.createPaymentMethod(newCardObject); if (payload.error) { @@ -43,6 +38,9 @@ export default ({ setEditingCard, customerCard }) => { const customer = await getCustomer({ auth: lmsData.auth }); setLMSData({ ...lmsData, customer }); setEditingCard(false); + if (returnURL) { + setTimeout(() => history.push(returnURL), 100); + } } setCardSubmitted(false); setProcessing(false); diff --git a/src/components/account/billing/index.js b/src/components/account/billing/index.js index bd721519f..95b3bd83d 100644 --- a/src/components/account/billing/index.js +++ b/src/components/account/billing/index.js @@ -2,7 +2,7 @@ import React, { useState } from 'react'; import { Row, Col, Card, CardBody } from '@nio/ui-kit'; import customerHasChargeableCard from '../../../util/stripe/customerHasChargeableCard'; -import useLMS from '../../../state/lmsData'; +import useLMS from '../../../state/stores/lmsData'; import defaultLMSData from '../../../state/defaults/defaultLMSData'; import StaticCard from './cardStatic'; import EditCard from './cardEdit'; diff --git a/src/components/account/billing/invoices.js b/src/components/account/billing/invoices.js index 8f777ce48..2dccd1bd4 100644 --- a/src/components/account/billing/invoices.js +++ b/src/components/account/billing/invoices.js @@ -3,7 +3,7 @@ import { Row, Col } from '@nio/ui-kit'; import useAsyncEffect from 'use-async-effect'; import getInvoices from '../../../api/lms/getInvoices'; -import useLMS from '../../../state/lmsData'; +import useLMS from '../../../state/stores/lmsData'; import defaultLMSData from '../../../state/defaults/defaultLMSData'; export default () => { diff --git a/src/components/account/index.js b/src/components/account/index.js index 3f3266753..e79a0e587 100644 --- a/src/components/account/index.js +++ b/src/components/account/index.js @@ -2,7 +2,7 @@ import React from 'react'; import { Redirect, Route, Switch } from 'react-router-dom'; import routes from './routes'; -import SubNav from '../shared/subnav'; +import SubNav from '../navs/subnav'; export default () => ( <> diff --git a/src/components/account/users/index.js b/src/components/account/users/index.js index 3cbbb70f4..ca9ec16c5 100644 --- a/src/components/account/users/index.js +++ b/src/components/account/users/index.js @@ -5,7 +5,7 @@ import useAsyncEffect from 'use-async-effect'; import DataTable from './datatable'; import AddUserForm from './add'; import getUsers from '../../../api/lms/getUsers'; -import useLMS from '../../../state/lmsData'; +import useLMS from '../../../state/stores/lmsData'; import defaultLMSData from '../../../state/defaults/defaultLMSData'; import customerUserColumns from '../../../util/datatable/customerUserColumns'; diff --git a/src/components/app.js b/src/components/app.js index 48a207fc6..65e0e2057 100644 --- a/src/components/app.js +++ b/src/components/app.js @@ -5,7 +5,7 @@ import Instances from './instances'; import Instance from './instance'; import Login from './login'; import Account from './account'; -import TopNav from './topnav'; +import TopNav from './navs/topnav'; export default () => (
diff --git a/src/components/instance/browse/csvuploader.js b/src/components/instance/browse/csvuploader.js index 31c654212..4c99f246a 100644 --- a/src/components/instance/browse/csvuploader.js +++ b/src/components/instance/browse/csvuploader.js @@ -26,13 +26,8 @@ export default ({ refreshInstance, instance_id, auth }) => { setStatus('validating'); const validatedCount = await getTotalRecords({ schema, table, auth }); setValidatedRecordCount(validatedCount); - - if (validatedCount < (newRecordCount + initialRecordCount)) { - return setTimeout(() => validateData(), 1000); - } - + if (validatedCount < (newRecordCount + initialRecordCount)) return setTimeout(() => validateData(), 1000); refreshInstance(Date.now()); - return setTimeout(() => { setStatus(false); history.push(`/instances/${instance_id}/browse/${schema}/${table}`); @@ -42,7 +37,6 @@ export default ({ refreshInstance, instance_id, auth }) => { // insert the processed data into HarperDB const insertData = async () => { if (!processedData) return false; - setStatus('inserting'); await csvDataLoad({ schema, table, data: processedData, auth }); setProcessedData(false); @@ -53,7 +47,6 @@ export default ({ refreshInstance, instance_id, auth }) => { const processData = (data) => { setStatus('processing'); setNewRecordCount(data.length - 1); - worker.postMessage(data); worker.addEventListener('message', (event) => { setProcessedData(event.data); diff --git a/src/components/instance/clustering/manage.js b/src/components/instance/clustering/manage.js index f918d8f34..1b8767383 100644 --- a/src/components/instance/clustering/manage.js +++ b/src/components/instance/clustering/manage.js @@ -5,7 +5,7 @@ import { useHistory } from 'react-router'; import useAsyncEffect from 'use-async-effect'; import EntityManager from '../../shared/entityManager'; -import useLMS from '../../../state/lmsData'; +import useLMS from '../../../state/stores/lmsData'; import DataTable from './datatable'; import buildInstanceStructure from '../../../util/buildInstanceStructure'; import handleSchemaTableRedirect from '../../../util/handleSchemaTableRedirect'; diff --git a/src/components/instance/config/index.js b/src/components/instance/config/index.js index 89bcd49bc..0438739fe 100644 --- a/src/components/instance/config/index.js +++ b/src/components/instance/config/index.js @@ -1,22 +1,26 @@ import React from 'react'; -import { Row, Col } from '@nio/ui-kit'; import UpdateInstanceForm from './updateInstanceForm'; -import InstanceDetails from './instanceDetails'; +import useLMS from '../../../state/stores/lmsData'; +import defaultLMSData from '../../../state/defaults/defaultLMSData'; -export default ({ auth, details, refreshInstance }) => ( - - - - - - - - -); +export default ({ auth, details, refreshInstance }) => { + const [{ products }] = useLMS(defaultLMSData); + + return ( + <> + resize instance + {products && details ? ( + + ) : ( + + )} + + ); +} diff --git a/src/components/instance/config/instanceDetails.js b/src/components/instance/config/instanceDetails.js index 20ca5b3a6..8b924aa80 100644 --- a/src/components/instance/config/instanceDetails.js +++ b/src/components/instance/config/instanceDetails.js @@ -1,115 +1,103 @@ import React from 'react'; import { Card, CardBody, Row, Col } from '@nio/ui-kit'; -export default ({ details }) => ( - <> - instance details +export default ({ details: { instance_name, is_local, host, port, is_ssl, instance_region, disk_space }, activeCompute, activeStorage }) => { + let totalPrice = 0; + if (activeCompute.price !== 'FREE') totalPrice += parseFloat(activeCompute.price); + if (activeStorage.price !== 'FREE') totalPrice += parseFloat(activeStorage.price); + + return ( - { details ? ( - <> - - - HarperDB Cloud® - - - {details.is_local ? 'no' : 'yes'} - - -
- - - Product ID - - - {details.stripe_product_id} - - -
- - - Instance Status - - - {details.status} - - -
+ + - + Instance Name - - {details.instance_name} + + {instance_name}
- - - Instance URL - - - {`http${details.domain_name || details.is_ssl ? 's' : ''}://${details.domain_name || details.host}:${details.port}`} - - - {details.instance_region && ( + {is_local ? ( <> + + + Host + + + {host} + +
- - AWS Region + + Port - - {details.instance_region} + + {port} +
+ + + Uses SSL + + + {is_ssl.toString()} + + +
+ + ) : ( + <> + + + Instance Region + + + {instance_region} + + +
+ + + Instance Storage + + + {disk_space} + + + {activeStorage.price === 'FREE' ? 'FREE' : `$${activeStorage.price}/${activeStorage.interval}`} + + +
)} -
- - RAM Allotment + + Instance RAM - - {details.instance_ram}GB + + {activeCompute.ram} - -
- - - Disk Space - - - {details.instance_disk_space_gigs}GB + + {activeCompute.price === 'FREE' ? 'FREE' : `$${activeCompute.price}/${activeCompute.interval}`}
- - Cost + + Instance Total Price - - ${details.is_local ? `${details.local_price_annual}/year` : `${details.cloud_price_monthly}/month`} + + {totalPrice ? `$${totalPrice.toFixed(2)}/${activeCompute.interval}` : 'FREE'} - {details.local_price_annual ? ( - <> -
- - - Next Charge Date - - - {details.exp_date ? new Date(details.exp_date).toLocaleDateString() : ''} - - - - ) : null} - - ) : ( - - )} +
+
- -); + ); +}; diff --git a/src/components/instance/config/updateInstanceForm.js b/src/components/instance/config/updateInstanceForm.js index 74410d69e..5147b1d5a 100644 --- a/src/components/instance/config/updateInstanceForm.js +++ b/src/components/instance/config/updateInstanceForm.js @@ -1,83 +1,138 @@ import React, { useState } from 'react'; -import { Button, Card, CardBody, Input, RadioCheckbox } from '@nio/ui-kit'; +import { Button, Card, CardBody, Col, Input, RadioCheckbox, Row } from '@nio/ui-kit'; import useAsyncEffect from 'use-async-effect'; +import { useHistory } from 'react-router'; -import useLMS from '../../../state/lmsData'; +import useLMS from '../../../state/stores/lmsData'; import updateInstance from '../../../api/lms/updateInstance'; import updateLicense from '../../../api/lms/updateLicense'; import setLicense from '../../../api/instance/setLicense'; import defaultLMSData from '../../../state/defaults/defaultLMSData'; -import defaultInstanceFormData from '../../../state/defaults/defaultInstanceFormData'; +import customerHasChargeableCard from '../../../util/stripe/customerHasChargeableCard'; -export default ({ instanceAuth, details, refreshInstance }) => { - const [{ auth, products }] = useLMS(defaultLMSData); - const [formData, updateForm] = useState(defaultInstanceFormData); - const instanceProducts = !products ? false : details.is_local ? products.local : products.cloud; - const defaultValue = instanceProducts && instanceProducts.find((p) => p.value === details.stripe_product_id); +export default ({ instanceAuth, details, refreshInstance, computeProducts, storageProducts }) => { + const [{ auth, customer, regions }] = useLMS(defaultLMSData); + const history = useHistory(); + const [formState, setFormState] = useState({ submitted: false, error: false }); + const [formData, updateForm] = useState({ instance_name: details.instance_name, stripe_plan_id: details.stripe_plan_id, storage_qty_gb: details.storage_qty_gb }); + const hasCard = customerHasChargeableCard(customer); + const thisRegion = details.instance_region && regions && regions.find((r) => r.value === details.instance_region); + + let totalPrice = 0; + const newComputePrice = computeProducts.find((p) => p.value === formData.stripe_plan_id); + if (newComputePrice.price !== 'FREE') totalPrice += parseFloat(newComputePrice.price); + + if (storageProducts) { + const newStoragePrice = storageProducts.find((p) => p.value === formData.storage_qty_gb); + if (newStoragePrice.price !== 'FREE') totalPrice += parseFloat(newStoragePrice.price); + } + + const hasChanged = details.stripe_plan_id !== formData.stripe_plan_id || details.storage_qty_gb !== formData.storage_qty_gb; useAsyncEffect(async () => { - if (formData.submitted) { - const { stripe_product_id, instance_name } = formData; - const { instance_id, customer_id, license_id, fingerprint } = details; + const { submitted } = formState; + if (submitted) { + const { stripe_product_id, instance_name, instance_id, customer_id, license_id, fingerprint, storage_qty_gb } = formData; const newLicense = await updateLicense({ auth, payload: { license_id, stripe_product_id, instance_id, customer_id, fingerprint } }); await setLicense({ auth: instanceAuth, key: newLicense.key, company: newLicense.company }); - await updateInstance({ auth, payload: { stripe_product_id, instance_id, customer_id, instance_name } }); - updateForm({ ...formData, submitted: false }); + await updateInstance({ auth, payload: { stripe_product_id, instance_id, customer_id, instance_name, storage_qty_gb } }); + setFormState({ submitted: false }); refreshInstance(Date.now()); } - }, [formData]); - - useAsyncEffect(() => { - if (details) { - updateForm({ ...formData, stripe_product_id: details.stripe_product_id, instance_name: details.instance_name }); - } - }, [details]); + }, [formState]); return ( - <> - resize instance - - - {products && defaultValue ? ( - <> -
Instance Name
-
- updateForm({ ...formData, instance_name: e.target.value, error: false })} - type="text" - title="instance_name" - value={formData.instance_name} - /> -
+ + +
Instance Name
+
+ updateForm({ ...formData, instance_name: e.target.value, error: false })} + type="text" + title="instance_name" + value={formData.instance_name} + /> +
-
Instance Type
-
- updateForm({ ...formData, stripe_product_id: value })} - options={instanceProducts} - value={formData.stripe_product_id || ''} - defaultValue={defaultValue} - /> -
+ {thisRegion && ( + <> +
Instance Region (no modification)
+
+ {thisRegion.label} +
+ + )} - - - ) : ( - + {storageProducts && ( + <> +
Storage Size
+
+ updateForm({ ...formData, storage_qty_gb: value })} + options={storageProducts} + value={formData.storage_qty_gb} + defaultValue={details.storage_qty_gb ? storageProducts.find((p) => p.value === details.storage_qty_gb) : storageProducts[0]} + /> +
+ + )} + +
Instance Type
+
+ {computeProducts && ( + updateForm({ ...formData, stripe_plan_id: value })} + options={computeProducts} + value={formData.stripe_plan_id} + defaultValue={details.stripe_plan_id ? computeProducts.find((p) => p.value === details.stripe_plan_id) : computeProducts[0]} + /> )} - - - +
+ +
+ + + + {!hasChanged ? 'Current' : 'New'} Price + + + ${totalPrice.toFixed(2)}/{computeProducts[0].interval} + + + +
+ + {hasChanged && totalPrice && !hasCard ? ( + + ) : ( + + )} +
+
); }; diff --git a/src/components/instance/index.js b/src/components/instance/index.js index 98b34ca93..dfa701451 100644 --- a/src/components/instance/index.js +++ b/src/components/instance/index.js @@ -2,10 +2,10 @@ import React, { useState } from 'react'; import { Route, Switch, useParams } from 'react-router-dom'; import useAsyncEffect from 'use-async-effect'; -import SubNav from '../shared/subnav'; +import SubNav from '../navs/subnav'; import routes from './routes'; -import useLMS from '../../state/lmsData'; -import useInstanceAuth from '../../state/instanceAuths'; +import useLMS from '../../state/stores/lmsData'; +import useInstanceAuth from '../../state/stores/instanceAuths'; import buildActiveInstanceObject from '../../util/buildActiveInstanceObject'; import defaultLMSData from '../../state/defaults/defaultLMSData'; import defaultActiveInstance from '../../state/defaults/defaultActiveInstance'; diff --git a/src/components/instances/index.js b/src/components/instances/index.js index 518a3a4bf..4a1383880 100644 --- a/src/components/instances/index.js +++ b/src/components/instances/index.js @@ -2,16 +2,17 @@ import React, { useState } from 'react'; import { Row } from '@nio/ui-kit'; import useAsyncEffect from 'use-async-effect'; -import useInstanceAuth from '../../state/instanceAuths'; +import useInstanceAuth from '../../state/stores/instanceAuths'; import InstanceCard from './list/instanceCard'; import NewInstanceCard from './list/newInstanceCard'; -import useLMS from '../../state/lmsData'; +import useLMS from '../../state/stores/lmsData'; import defaultLMSData from '../../state/defaults/defaultLMSData'; import getInstances from '../../api/lms/getInstances'; import getLicenses from '../../api/lms/getLicenses'; import getProducts from '../../api/lms/getProducts'; +import getRegions from '../../api/lms/getRegions'; import filterInstances from '../../util/filterInstances'; -import SubNav from '../shared/subnav'; +import SubNav from '../navs/subnav'; export default () => { const [instanceAuths, setInstanceAuths] = useInstanceAuth({}); @@ -25,7 +26,8 @@ export default () => { const instances = await getInstances({ auth: lmsData.auth }); const licenses = await getLicenses({ auth: lmsData.auth }); const products = await getProducts({ auth: lmsData.auth }); - setLMSData({ ...lmsData, instances, licenses, products }); + const regions = await getRegions({ auth: lmsData.auth }); + setLMSData({ ...lmsData, instances, licenses, products, regions }); }, []); useAsyncEffect(() => { @@ -50,6 +52,8 @@ export default () => { p.value === i.stripe_plan_id)} + storage={i.is_local ? { disk_space: 'n/a' } : lmsData.products && lmsData.products.cloudStorage.find((p) => p.value === i.storage_qty_gb)} hasAuth={instanceAuths[i.id]} setAuth={({ id, user, pass }) => setInstanceAuths({ ...instanceAuths, [id]: user && pass ? { user, pass } : false })} /> diff --git a/src/components/instances/list/cardBack.js b/src/components/instances/list/cardBack.js index e2184fbd6..497d67c40 100644 --- a/src/components/instances/list/cardBack.js +++ b/src/components/instances/list/cardBack.js @@ -3,11 +3,11 @@ import { Button, Card, CardBody, Input, Row, Col } from '@nio/ui-kit'; import useAsyncEffect from 'use-async-effect'; import { useAlert } from 'react-alert'; import queryInstance from '../../../api/queryInstance'; -import defaultFormData from '../../../state/defaults/defaultAuthFormData'; +import defaultAuthFormData from '../../../state/defaults/defaultAuthFormData'; export default ({ id, host, port, is_ssl, setAuth, flipCard, flipState }) => { const alert = useAlert(); - const [formData, updateForm] = useState(defaultFormData); + const [formData, updateForm] = useState(defaultAuthFormData); useAsyncEffect(async () => { if (formData.submitted) { @@ -16,7 +16,7 @@ export default ({ id, host, port, is_ssl, setAuth, flipCard, flipState }) => { alert.error(instance.error); updateForm({ ...formData, error: instance.error, submitted: false }); } else { - updateForm(defaultFormData); + updateForm(defaultAuthFormData); setAuth({ id, user: formData.user, pass: formData.pass }); flipCard(); } @@ -44,7 +44,7 @@ export default ({ id, host, port, is_ssl, setAuth, flipCard, flipState }) => { + + + + + + + + + {formState.error && ( +
+
+ {formState.error} +
+ )} ); }; diff --git a/src/components/instances/new/confirm.js b/src/components/instances/new/confirm.js index 1118fd80b..10a94ac9c 100644 --- a/src/components/instances/new/confirm.js +++ b/src/components/instances/new/confirm.js @@ -1,95 +1,164 @@ -import React from 'react'; +import React, { useState } from 'react'; import { Col, Row, Button, Card, CardBody } from '@nio/ui-kit'; +import useAsyncEffect from 'use-async-effect'; -export default ({ instancePrice, instanceDetails, instanceSpecs, setConfirmed }) => ( - <> - - - - - Monthly Cost - - - ${instancePrice} - - -
- - - Instance Name - - - {instanceDetails.instance_name} - - -
- - - Admin User - - - {instanceDetails.user} - - -
- - - Admin Password - - - {instanceDetails.pass} - - -
- - - Instance RAM - - - {instanceSpecs.instance_ram}GB - - -
- {instanceDetails.is_local ? ( +import addInstance from '../../../api/lms/addInstance'; + +export default ({ newInstance, computeProduct, storageProduct, setShowForm, setPurchaseStep }) => { + const [formData, updateForm] = useState({ submitted: false, error: false }); + let totalPrice = 0; + if (computeProduct.price !== 'FREE') totalPrice += parseFloat(computeProduct.price); + if (storageProduct.price !== 'FREE') totalPrice += parseFloat(storageProduct.price); + + useAsyncEffect(async () => { + const { submitted } = formData; + if (submitted) { + console.log(newInstance); + + const response = await addInstance(newInstance); + if (response.result) { + updateForm({ submitted: false, error: false }); + setShowForm(false); + } else { + updateForm({ submitted: false, error: response.message }); + } + } + }, [formData]); + + return ( + <> + + + + + Instance Name + + + {newInstance.instance_name} + + +
+ + + Admin User + + + {newInstance.user} + + +
+ + + Admin Password + + + {newInstance.pass} + + +
+ {newInstance.is_local ? ( + <> + + + Host + + + {newInstance.host} + + +
+ + + Port + + + {newInstance.port} + + +
+ + + Uses SSL + + + {newInstance.is_ssl.toString()} + + +
+ + ) : ( + <> + + + Instance Region + + + {newInstance.instance_region} + + +
+ + + Instance Storage + + + {storageProduct.disk_space} + + + {storageProduct.price === 'FREE' ? 'FREE' : `$${storageProduct.price}/${storageProduct.interval}`} + + +
+ + )} + + + Instance RAM + + + {computeProduct.ram} + + + {computeProduct.price === 'FREE' ? 'FREE' : `$${computeProduct.price}/${computeProduct.interval}`} + + +
- Uses SSL + Instance Total Price - {instanceDetails.is_ssl.toString()} + {totalPrice ? `$${totalPrice.toFixed(2)}/${computeProduct.interval}` : 'FREE'} - ) : ( - <> - - - Instance Disk Space - - - {instanceSpecs.instance_disk_space_gigs}GB - - -
- - - Instance Region - - - {instanceDetails.instance_region} - - - - )} -
-
- - -); + +
+
+ + + + + + + + + + ); +}; diff --git a/src/components/instances/new/local.js b/src/components/instances/new/local.js index 1b8ee8b15..9207419f5 100644 --- a/src/components/instances/new/local.js +++ b/src/components/instances/new/local.js @@ -1,139 +1,75 @@ import React, { useState } from 'react'; -import { Col, Input, Row, RadioCheckbox, Button, Card, CardBody } from '@nio/ui-kit'; +import { RadioCheckbox, Button, Card, CardBody, Col, Row } from '@nio/ui-kit'; import useAsyncEffect from 'use-async-effect'; -import defaultInstanceFormData from '../../../state/defaults/defaultInstanceFormData'; +export default ({ products, hasCard, newInstance, setNewInstance, setPurchaseStep }) => { + const [formState, setFormState] = useState({ submitted: false, error: false }); + const [formData, updateForm] = useState({ stripe_plan_id: newInstance.stripe_plan_id || products[0].value }); -export default ({ products, setInstanceDetails, needsCard }) => { - const [formData, updateForm] = useState(defaultInstanceFormData); - - useAsyncEffect(async () => { - if (formData.submitted) { - formData.is_local = true; - setInstanceDetails(formData); - } - }, [formData]); + const computePrice = products && products.find((p) => p.value === formData.stripe_plan_id).price; + const needsCard = products && !hasCard && computePrice && (computePrice !== 'FREE'); useAsyncEffect(() => { - if (products) { - updateForm({ ...formData, stripe_product_id: products[0].value }); + const { submitted } = formState; + const { stripe_plan_id } = formData; + if (submitted) { + if (stripe_plan_id) { + setNewInstance({ ...newInstance, stripe_plan_id }); + setPurchaseStep(needsCard ? 'payment' : 'confirm'); + } else { + setFormState({ submitted: false, error: 'All fields must be filled out.' }); + } } - }, [products]); + }, [formState]); return ( <> -
Instance Name
-
- updateForm({ ...formData, instance_name: e.target.value, error: false })} - type="text" - title="instance_name" - value={formData.instance_name} - /> -
- - -
Admin Credentials
-
- - - Username - - - updateForm({ ...formData, user: e.target.value, error: false })} - type="text" - title="username" - value={formData.user} - /> - - -
- - - Password - - - updateForm({ ...formData, pass: e.target.value, error: false })} - type="password" - title="password" - value={formData.pass} - /> - - -
-
Instance Type
-
+
updateForm({ ...formData, stripe_product_id: value })} + onChange={(value) => updateForm({ ...formData, stripe_plan_id: value })} options={products} - value={formData.stripe_product_id || ''} - defaultValue={products[0]} + value={formData.stripe_plan_id} + defaultValue={newInstance.stripe_plan_id ? products.find((p) => p.value === newInstance.stripe_plan_id) : products[0]} />
- -
Instance Details
-
- - - Host - - - updateForm({ ...formData, host: e.target.value, error: false })} - type="text" - title="host" - value={formData.host || ''} - /> - - -
- - - Port - - - updateForm({ ...formData, port: e.target.value, error: false })} - type="number" - title="port" - value={formData.port || ''} - /> - - -
- - - SSL - - - updateForm({ ...formData, is_ssl: value || false, error: false })} - options={{ label: '', value: true }} - value={formData.is_ssl} - /> - - -
- - + + + + + + + + + {formState.error && ( +
+
+ {formState.error} +
+ )} ); }; diff --git a/src/components/instances/new/meta.js b/src/components/instances/new/meta.js new file mode 100644 index 000000000..b6c14993c --- /dev/null +++ b/src/components/instances/new/meta.js @@ -0,0 +1,172 @@ +import React, { useState } from 'react'; +import { Col, Input, Row, Button, Card, CardBody, RadioCheckbox } from '@nio/ui-kit'; +import useAsyncEffect from 'use-async-effect'; + +export default ({ newInstance, setNewInstance, setPurchaseStep }) => { + const [formState, setFormState] = useState({ submitted: false, error: false }); + const [formData, updateForm] = useState({ + instance_name: newInstance.instance_name || '', + user: newInstance.user || '', + pass: newInstance.pass || '', + host: newInstance.host || '', + port: newInstance.port || '', + is_ssl: newInstance.is_ssl || false, + }); + + useAsyncEffect(() => { + const { submitted } = formState; + const { instance_name, user, pass, host, port, is_ssl } = formData; + if (submitted) { + if (newInstance.is_local) { + if ((instance_name.length && user.length && pass.length && host.length && port.length)) { + setNewInstance({ ...newInstance, instance_name, user, pass, host, port, is_ssl }); + setPurchaseStep('details_local'); + } else { + setFormState({ submitted: false, error: 'All fields must be filled out.' }); + } + } else if ((instance_name.length && user.length && pass.length)) { + setNewInstance({ ...newInstance, instance_name, user, pass, is_ssl: true }); + setPurchaseStep('details_cloud'); + } else { + setFormState({ submitted: false, error: 'All fields must be filled out.' }); + } + } + }, [formState]); + + return ( + <> + + +
Instance Name
+
+ + + example: "HDB-1" + + + updateForm({ ...formData, instance_name: e.target.value, error: false })} + type="text" + title="instance_name" + value={formData.instance_name} + /> + + +
+ +
Admin Credentials
+
+ + + Username + + + updateForm({ ...formData, user: e.target.value, error: false })} + type="text" + title="username" + value={formData.user} + /> + + +
+ + + Password + + + updateForm({ ...formData, pass: e.target.value, error: false })} + type="password" + title="password" + value={formData.pass} + /> + + +
+ + {newInstance.is_local && ( + <> +
Instance Details
+
+ + + Host + + + updateForm({ ...formData, host: e.target.value, error: false })} + type="text" + title="host" + value={formData.host || ''} + /> + + +
+ + + Port + + + updateForm({ ...formData, port: e.target.value, error: false })} + type="number" + title="port" + value={formData.port || ''} + /> + + +
+ + + SSL + + + updateForm({ ...formData, is_ssl: value || false, error: false })} + options={{ label: '', value: true }} + value={formData.is_ssl} + /> + + +
+ + )} + +
+
+ + + + + + + + + {formState.error && ( +
+
+ {formState.error} +
+ )} + + ); +}; diff --git a/src/components/instances/new/modal.js b/src/components/instances/new/modal.js index 4ea8a90f5..195544df5 100644 --- a/src/components/instances/new/modal.js +++ b/src/components/instances/new/modal.js @@ -2,108 +2,83 @@ import React, { useState } from 'react'; import { Modal, ModalHeader, ModalBody, Loader } from '@nio/ui-kit'; import useAsyncEffect from 'use-async-effect'; -import useLMS from '../../../state/lmsData'; +import useLMS from '../../../state/stores/lmsData'; import defaultLMSData from '../../../state/defaults/defaultLMSData'; -import getInstancePrice from '../../../util/stripe/getInstancePrice'; -import getProducts from '../../../api/lms/getProducts'; -import getRegions from '../../../api/lms/getRegions'; import customerHasChargeableCard from '../../../util/stripe/customerHasChargeableCard'; import getCustomer from '../../../api/lms/getCustomer'; -import addInstance from '../../../api/lms/addInstance'; +import steps from '../../../util/addInstanceSteps'; +import InstanceTypeForm from './type'; +import InstanceMetadataForm from './meta'; import LocalInstanceForm from './local'; import CloudInstanceForm from './cloud'; -import InstanceTypeForm from './type'; import CustomerPaymentForm from './payment'; import ConfirmOrderForm from './confirm'; export default ({ setShowForm }) => { const [lmsData, setLMSData] = useLMS(defaultLMSData); - const [products, setProducts] = useState(false); - const [regions, setRegions] = useState(false); - const [purchaseStep, setPurchaseStep] = useState('Select Instance Type'); - const [instanceType, setInstanceType] = useState(false); - const [instancePrice, setInstancePrice] = useState(false); - const [instanceDetails, setInstanceDetails] = useState(false); - const [paymentInfo, setPaymentInfo] = useState(false); - const [confirmed, setConfirmed] = useState(false); + const [purchaseStep, setPurchaseStep] = useState('type'); + const [newInstance, setNewInstance] = useState({}); const hasCard = customerHasChargeableCard(lmsData.customer); - const needsCard = instancePrice && !hasCard; - const instanceSpecs = products && products.raw.find((t) => t.stripe_product_id === instanceDetails.stripe_product_id); useAsyncEffect(async () => { - const newProducts = await getProducts({ auth: lmsData.auth }); - setProducts(newProducts); - const newRegions = await getRegions({ auth: lmsData.auth }); - setRegions(newRegions); const customer = await getCustomer({ auth: lmsData.auth }); setLMSData({ ...lmsData, customer }); }, []); - useAsyncEffect(() => { - if (instanceType) setPurchaseStep('Instance Details'); - }, [instanceType]); - - useAsyncEffect(() => { - if (instanceDetails) { - const selectedInstancePrice = getInstancePrice({ products: products.raw, instanceDetails }); - setInstancePrice(selectedInstancePrice); - if (selectedInstancePrice && !hasCard) { - setPurchaseStep('Payment Details'); - } else { - setPurchaseStep('Confirm Instance Details'); - } - } - }, [instanceDetails]); - - useAsyncEffect(() => { - if (paymentInfo) setPurchaseStep('Confirm Instance Details'); - }, [paymentInfo]); - - useAsyncEffect(async () => { - if (confirmed) { - await addInstance({ auth: lmsData.auth, payload: { ...instanceDetails, customer_id: lmsData.customer.customer_id } }); - setShowForm(false); - } - }, [confirmed]); - return ( - setShowForm(false)}> + setShowForm(false)}> setShowForm(false)}> - {purchaseStep} + {steps[purchaseStep].label} - {!products ? ( + {!lmsData.products ? ( - ) : purchaseStep === 'Select Instance Type' ? ( + ) : purchaseStep === 'type' ? ( + ) : purchaseStep === 'meta' ? ( + - ) : purchaseStep === 'Instance Details' && instanceType === 'local' ? ( + ) : purchaseStep === 'details_local' ? ( - ) : purchaseStep === 'Instance Details' && instanceType === 'cloud' ? ( + ) : purchaseStep === 'details_cloud' ? ( - ) : purchaseStep === 'Payment Details' ? ( + ) : purchaseStep === 'payment' ? ( p.value === newInstance.stripe_plan_id)} + storageProduct={newInstance.is_local ? { price: 'FREE' } : lmsData.products.cloudStorage.find((p) => p.value === newInstance.storage_qty_gb)} + setPurchaseStep={setPurchaseStep} /> - ) : purchaseStep === 'Confirm Instance Details' ? ( + ) : purchaseStep === 'confirm' ? ( p.value === newInstance.stripe_plan_id)} + storageProduct={newInstance.is_local ? { price: 'FREE' } : lmsData.products.cloudStorage.find((p) => p.value === newInstance.storage_qty_gb)} + setShowForm={setShowForm} + setPurchaseStep={setPurchaseStep} /> ) : null} diff --git a/src/components/instances/new/payment.js b/src/components/instances/new/payment.js index 7cf652a4b..8e4dbbfc5 100644 --- a/src/components/instances/new/payment.js +++ b/src/components/instances/new/payment.js @@ -1,54 +1,171 @@ import React, { useState } from 'react'; -import { CardElement, useStripe, useElements } from '@stripe/react-stripe-js'; -import { Button, Card, CardBody } from '@nio/ui-kit'; +import { CardNumberElement, CardExpiryElement, CardCvcElement, useElements, useStripe } from '@stripe/react-stripe-js'; +import { Button, Card, CardBody, Col, Input, Row } from '@nio/ui-kit'; +import useAsyncEffect from 'use-async-effect'; import cardOptions from '../../../util/stripe/cardOptions'; +import addPaymentMethod from '../../../api/lms/addPaymentMethod'; +import getCustomer from '../../../api/lms/getCustomer'; +import useLMS from '../../../state/stores/lmsData'; +import defaultLMSData from '../../../state/defaults/defaultLMSData'; -export default ({ setPaymentInfo, instancePrice, instanceInterval }) => { - const stripe = useStripe(); - const elements = useElements(); +export default ({ hasCard, computeProduct, newInstance, storageProduct, setPurchaseStep }) => { + const [lmsData, setLMSData] = useLMS(defaultLMSData); + const [postalCode, setPostalCode] = useState(false); + const [cardSubmitted, setCardSubmitted] = useState(false); const [error, setError] = useState(null); const [cardComplete, setCardComplete] = useState(false); const [processing, setProcessing] = useState(false); - const [paymentMethod, setPaymentMethod] = useState(null); - - const handleSubmit = async () => { - if (!stripe || !elements) return; - if (error) { elements.getElement('card').focus(); return; } - if (cardComplete) setProcessing(true); + const [addedCard, setAddedCard] = useState(false); + const stripe = useStripe(); + const elements = useElements(); - const payload = await stripe.createPaymentMethod({ type: 'card', card: elements.getElement(CardElement) }); - setProcessing(false); + useAsyncEffect(async () => { + if (cardSubmitted) { + if (cardComplete) setProcessing(true); + const newCardObject = { type: 'card', card: elements.getElement(CardNumberElement), billing_details: { address: { postal_code: postalCode } } }; + const payload = await stripe.createPaymentMethod(newCardObject); - if (payload.error) { - setError(payload.error); - } else { - setPaymentMethod(payload.paymentMethod); + if (payload.error) { + setError(payload.error); + setProcessing(false); + } else { + await addPaymentMethod({ payment_method_id: payload.paymentMethod.id, stripe_customer_id: lmsData.customer.stripe_customer_object.id, auth: lmsData.auth }); + const customer = await getCustomer({ auth: lmsData.auth }); + setLMSData({ ...lmsData, customer }); + setProcessing(false); + setAddedCard(true); + } + setCardSubmitted(false); } - }; + }, [cardSubmitted]); return ( - {paymentMethod ? ( - <> -
Card Added Successfully
-
- Manage payment forms on your account page. -
- - - ) : ( - <> -
Credit Card Details

- The instance type you selected has a cost of ${instancePrice}/{instanceInterval}. Please add a credit card to your account using the form below. If you registered using a promo code, your card will not be charged until your promo credits run out. -
- { setError(e.error); setCardComplete(e.complete); }} /> -
- - {error &&
{error.message}
} - - )} +
Credit Card Details
+
+ {addedCard || hasCard ? ( + <> + Credit Card was successfully added to your account. +
+ + + + + + + + + + ) : ( + <> + {computeProduct.price !== 'FREE' &&
The selected instance type has a cost of ${computeProduct.price}/{computeProduct.interval}.
} + {storageProduct.price !== 'FREE' &&
The selected storage size has a cost of ${storageProduct.price}/{storageProduct.interval}.
} + Please add a credit card to your account using the form below. If you registered using a promo code, your card will not be charged until your promo credits run out. +
+ + + card number + + +
+ { setError(e.error); setCardComplete(e.complete); }} + /> +
+ +
+
+ + + expiration + + +
+ { setError(e.error); setCardComplete(e.complete); }} + /> +
+ +
+
+ + + cvcc + + +
+ { setError(e.error); setCardComplete(e.complete); }} + /> +
+ +
+
+ + + billing postal code + + + setPostalCode(e.target.value)} + /> + + +
+ + + + + + + + + {error && ( +
+
+ {error.message} +
+ )} + + )} +
); diff --git a/src/components/instances/new/type.js b/src/components/instances/new/type.js index fc360eca6..b2a713844 100644 --- a/src/components/instances/new/type.js +++ b/src/components/instances/new/type.js @@ -1,45 +1,68 @@ -import React from 'react'; +import React, { useState } from 'react'; import { Row, Col, Card, CardBody, Button } from '@nio/ui-kit'; +import useAsyncEffect from 'use-async-effect'; -export default ({ setInstanceType }) => ( - - - - -
Add HarperDB Cloud Instance
-
-
    -
  • Free License Tier Available!
  • -
  • Hosted on AWS
  • -
  • Managed by HarperDB
  • -
  • HarperDB License Included
  • -
  • 24/7 Customer Support
  • -
  • Choose RAM and Disk Size
  • -
  • Scale On Demand
  • -
-
- -
-
- - - - -
Register A Local Instance
-
-
    -
  • Free License Tier Available!
  • -
  • For HarperDB Instances You Install
  • -
  • Browse Instance Data
  • -
  • Configure Users, Roles, and Clustering
  • -
  • Manage Instance Licenses
  • -
  • Handle Version Upgrades
  • -
  • Instance Credentials Stay Local
  • -
-
- -
-
- -
-); +export default ({ newInstance, setNewInstance, setPurchaseStep }) => { + const [formData, updateForm] = useState({ + is_local: undefined, + storage_qty_gb: false, + stripe_plan_id: false, + instance_region: false, + stripe_product_id: false, + stripe_storage_product_id: false, + stripe_storage_plan_id: false, + }); + + useAsyncEffect(() => { + const { is_local } = formData; + if (is_local !== undefined) { + setNewInstance({ ...newInstance, ...formData }); + setPurchaseStep('meta'); + } + }, [formData]); + + useAsyncEffect(() => setNewInstance({ ...formData }), []); + + return ( + + + + +
Add HarperDB Cloud Instance
+
+
    +
  • Free License Tier Available!
  • +
  • Hosted on AWS
  • +
  • Managed by HarperDB
  • +
  • HarperDB License Included
  • +
  • 24/7 Customer Support
  • +
  • Choose RAM and Disk Size
  • +
  • Scale On Demand
  • +
+
+ +
+
+ + + + +
Register A Local Instance
+
+
    +
  • Free License Tier Available!
  • +
  • For HarperDB Instances You Install
  • +
  • Browse Instance Data
  • +
  • Configure Users, Roles, and Clustering
  • +
  • Manage Instance Licenses
  • +
  • Handle Version Upgrades
  • +
  • Instance Credentials Stay Local
  • +
+
+ +
+
+ +
+ ); +}; diff --git a/src/components/login.js b/src/components/login/index.js similarity index 87% rename from src/components/login.js rename to src/components/login/index.js index 596da7750..3fc7e327b 100644 --- a/src/components/login.js +++ b/src/components/login/index.js @@ -3,15 +3,15 @@ import { Card, CardBody, Input, Button } from '@nio/ui-kit'; import useAsyncEffect from 'use-async-effect'; import { useHistory } from 'react-router'; -import useLMS from '../state/lmsData'; -import getUser from '../api/lms/getUser'; -import getCustomer from '../api/lms/getCustomer'; -import defaultFormData from '../state/defaults/defaultAuthFormData'; -import defaultLMSData from '../state/defaults/defaultLMSData'; +import useLMS from '../../state/stores/lmsData'; +import getUser from '../../api/lms/getUser'; +import getCustomer from '../../api/lms/getCustomer'; +import defaultAuthFormData from '../../state/defaults/defaultAuthFormData'; +import defaultLMSData from '../../state/defaults/defaultLMSData'; export default () => { const [lmsData, setLMSData] = useLMS(defaultLMSData); - const [formData, updateForm] = useState(defaultFormData); + const [formData, updateForm] = useState(defaultAuthFormData); const history = useHistory(); useAsyncEffect(async () => { diff --git a/src/components/shared/subnav.js b/src/components/navs/subnav.js similarity index 100% rename from src/components/shared/subnav.js rename to src/components/navs/subnav.js diff --git a/src/components/topnav.js b/src/components/navs/topnav.js similarity index 93% rename from src/components/topnav.js rename to src/components/navs/topnav.js index b34d63209..7d8071c6b 100644 --- a/src/components/topnav.js +++ b/src/components/navs/topnav.js @@ -3,8 +3,8 @@ import { Navbar, Nav, NavItem, NavLink as DumbLink } from '@nio/ui-kit'; import { useHistory } from 'react-router'; import { NavLink } from 'react-router-dom'; -import useLMS from '../state/lmsData'; -import defaultLMSData from '../state/defaults/defaultLMSData'; +import useLMS from '../../state/stores/lmsData'; +import defaultLMSData from '../../state/defaults/defaultLMSData'; export default () => { const history = useHistory(); diff --git a/src/components/shared/entityManager.js b/src/components/shared/entityManager.js index 632a26e47..5108ce74b 100644 --- a/src/components/shared/entityManager.js +++ b/src/components/shared/entityManager.js @@ -40,9 +40,7 @@ export default ({ items, activeItem, activeSchema = false, auth, refreshInstance auth={auth} refreshInstance={refreshInstance} /> - )) : ( - null - )} + )) : null} {((items && !items.length) || isCreating) && ( { const auth = { ...instanceAuths[instance_id], host: thisInstance.host, port: thisInstance.port, is_ssl: thisInstance.is_ssl }; const instance = await queryInstance({ operation: 'describe_all' }, auth); const license = lmsData.licenses.find((l) => l.instance_id === instance_id); - const product = lmsData.products.raw.find((p) => p.stripe_product_id === thisInstance.stripe_product_id); + const product = lmsData.products[thisInstance.is_local ? 'localCompute' : 'cloudCompute'].find((p) => p.value === thisInstance.stripe_plan_id); + const storage = thisInstance.is_local ? null : lmsData.products.cloudStorage.find((p) => p.value === thisInstance.storage_qty_gb); if (instance.error) { return instance; @@ -15,5 +16,5 @@ export default async ({ instance_id, instanceAuths, lmsData }) => { const structure = browseTableColumns(instance); const network = await clusterStatus(auth); - return { auth, structure, network, details: { ...license, ...product, ...thisInstance } }; + return { auth, structure, network, details: { ...license, ...product, ...storage, ...thisInstance } }; }; diff --git a/src/util/stripe/getInstancePrice.js b/src/util/stripe/getInstancePrice.js index 83689fffb..ae05764eb 100644 --- a/src/util/stripe/getInstancePrice.js +++ b/src/util/stripe/getInstancePrice.js @@ -1,4 +1,4 @@ -export default ({ products, instanceDetails }) => { - const selectedProduct = products.find((p) => p.stripe_product_id === instanceDetails.stripe_product_id); +export default ({ products, plan_id }) => { + const selectedPlan = products.find((p) => p.stripe_product_id === instanceDetails.stripe_product_id); return instanceDetails.is_local ? selectedProduct.local_price_annual : selectedProduct.cloud_price_monthly; }; From 668a940660bcbbed8746992a6330ed25d98c170d Mon Sep 17 00:00:00 2001 From: deliciousmonster Date: Mon, 2 Mar 2020 17:41:22 -0700 Subject: [PATCH 19/19] addCustomer form hits the real API shape data to have body element split out appData from auth Data Instance Users/Add User --- mock_data/LMS_API.aws_regions.json | 159 +- mock_data/LMS_API.customer.json | 117 +- mock_data/LMS_API.customer_users.json | 77 +- mock_data/LMS_API.instances.json | 311 +- mock_data/LMS_API.invoices.json | 10131 +++++++++++++++- mock_data/LMS_API.licenses.json | 199 +- mock_data/LMS_API.user.json | 13 +- package-lock.json | 12 +- package.json | 4 +- src/api/instance/addUser.js | 4 + src/api/instance/dropUser.js | 3 + src/api/instance/listUsers.js | 4 + src/api/lms/addCustomer.js | 12 + src/api/lms/addInstance.js | 16 +- src/api/lms/addLicense.js | 16 +- src/api/lms/addPaymentMethod.js | 16 +- src/api/lms/addTCAcceptance.js | 16 +- src/api/lms/addUser.js | 16 +- src/api/lms/getCustomer.js | 14 +- src/api/lms/getInstances.js | 20 +- src/api/lms/getInvoices.js | 14 +- src/api/lms/getLicenses.js | 14 +- src/api/lms/getProducts.js | 12 +- src/api/lms/getRegions.js | 13 +- src/api/lms/getUser.js | 15 +- src/api/lms/getUsers.js | 14 +- src/api/lms/removeInstance.js | 16 +- src/api/lms/removePaymentMethod.js | 16 +- src/api/lms/removeUser.js | 6 +- src/api/lms/updateInstance.js | 16 +- src/api/lms/updateLicense.js | 16 +- src/api/queryLMS.js | 68 +- src/assets/styles/_card.scss | 20 +- src/assets/styles/_csv-input.scss | 4 +- src/assets/styles/_forms.scss | 15 +- src/assets/styles/_login.scss | 2 +- src/assets/styles/_react-table.scss | 1 + src/components/account/billing/cardEdit.js | 17 +- src/components/account/billing/index.js | 17 +- src/components/account/billing/invoices.js | 10 +- src/components/account/profile/password.js | 4 +- src/components/account/users/add.js | 65 +- src/components/account/users/index.js | 15 +- src/components/app.js | 11 +- src/components/instance/browse/csvuploader.js | 9 +- .../instance/clustering/datatable.js | 2 +- src/components/instance/clustering/manage.js | 8 +- .../instance/clustering/setup_port.js | 2 +- .../instance/clustering/setup_user.js | 2 +- src/components/instance/config/index.js | 6 +- .../instance/config/updateInstanceForm.js | 32 +- src/components/instance/index.js | 24 +- src/components/instance/users/add.js | 98 + src/components/instance/users/datatable.js | 44 + src/components/instance/users/index.js | 57 +- src/components/instances/index.js | 44 +- src/components/instances/list/cardFront.js | 5 +- src/components/instances/list/instanceCard.js | 1 - .../instances/list/newInstanceCard.js | 2 +- src/components/instances/new/cloud.js | 8 +- src/components/instances/new/confirm.js | 14 +- .../instances/new/{modal.js => index.js} | 40 +- src/components/instances/new/local.js | 4 +- src/components/instances/new/meta.js | 8 +- src/components/instances/new/payment.js | 19 +- src/components/login/index.js | 61 +- src/components/navs/topnav.js | 15 +- src/components/register/index.js | 140 + src/components/shared/protectedRoute.js | 17 + src/state/defaults/defaultAppData.js | 1 + src/state/defaults/defaultAuthFormData.js | 2 - src/state/defaults/defaultLMSAuth.js | 1 + src/state/defaults/defaultLMSData.js | 1 - src/state/defaults/defaultPasswordFormData.js | 1 + src/state/defaults/defaultProfileFormData.js | 2 +- src/state/stores/appData.js | 3 + src/state/stores/{lmsData.js => lmsAuth.js} | 0 src/util/buildActiveInstanceObject.js | 14 +- src/util/datatable/customerUserColumns.js | 4 +- src/util/datatable/instanceUserColumns.js | 24 + 80 files changed, 11419 insertions(+), 857 deletions(-) create mode 100644 src/api/instance/addUser.js create mode 100644 src/api/instance/dropUser.js create mode 100644 src/api/instance/listUsers.js create mode 100644 src/api/lms/addCustomer.js create mode 100644 src/components/instance/users/add.js create mode 100644 src/components/instance/users/datatable.js rename src/components/instances/new/{modal.js => index.js} (73%) create mode 100644 src/components/register/index.js create mode 100644 src/components/shared/protectedRoute.js create mode 100644 src/state/defaults/defaultAppData.js create mode 100644 src/state/defaults/defaultLMSAuth.js delete mode 100644 src/state/defaults/defaultLMSData.js create mode 100644 src/state/defaults/defaultPasswordFormData.js create mode 100644 src/state/stores/appData.js rename src/state/stores/{lmsData.js => lmsAuth.js} (100%) create mode 100644 src/util/datatable/instanceUserColumns.js diff --git a/mock_data/LMS_API.aws_regions.json b/mock_data/LMS_API.aws_regions.json index 31b9ef59c..0a5e9415c 100644 --- a/mock_data/LMS_API.aws_regions.json +++ b/mock_data/LMS_API.aws_regions.json @@ -1,78 +1,81 @@ -[ - { - "label": "US East (N. Virginia)", - "value": "us-east-1" - }, - { - "label": "US East (Ohio)", - "value": "us-east-2" - }, - { - "label": "US West (N. California)", - "value": "us-west-1" - }, - { - "label": "US West (Oregon)", - "value": "us-west-2" - }, - { - "label": "Asia Pacific (Hong Kong)", - "value": "ap-east-1" - }, - { - "label": "Asia Pacific (Mumbai)", - "value": "ap-south-1" - }, - { - "label": "Asia Pacific (Osaka-Local)", - "value": "ap-northeast-3" - }, - { - "label": "Asia Pacific (Seoul)", - "value": "ap-northeast-2" - }, - { - "label": "Asia Pacific (Singapore)", - "value": "ap-southeast-1" - }, - { - "label": "Asia Pacific (Sydney)", - "value": "ap-southeast-2" - }, - { - "label": "Asia Pacific (Tokyo)", - "value": "ap-northeast-1" - }, - { - "label": "Canada (Central)", - "value": "ca-central-1" - }, - { - "label": "Europe (Frankfurt)", - "value": "eu-central-1" - }, - { - "label": "Europe (Ireland)", - "value": "eu-west-1" - }, - { - "label": "Europe (London)", - "value": "eu-west-2" - }, - { - "label": "Europe (Paris)", - "value": "eu-west-3" - }, - { - "label": "Europe (Stockholm)", - "value": "eu-north-1" - }, - { - "label": "Middle East (Bahrain)", - "value": "me-south-1" - }, - { - "label": "South America (São Paulo)", - "value": "sa-east-1" - } -] +{ + "statusCode": 200, + "body": [ + { + "label": "US East (N. Virginia)", + "value": "us-east-1" + }, + { + "label": "US East (Ohio)", + "value": "us-east-2" + }, + { + "label": "US West (N. California)", + "value": "us-west-1" + }, + { + "label": "US West (Oregon)", + "value": "us-west-2" + }, + { + "label": "Asia Pacific (Hong Kong)", + "value": "ap-east-1" + }, + { + "label": "Asia Pacific (Mumbai)", + "value": "ap-south-1" + }, + { + "label": "Asia Pacific (Osaka-Local)", + "value": "ap-northeast-3" + }, + { + "label": "Asia Pacific (Seoul)", + "value": "ap-northeast-2" + }, + { + "label": "Asia Pacific (Singapore)", + "value": "ap-southeast-1" + }, + { + "label": "Asia Pacific (Sydney)", + "value": "ap-southeast-2" + }, + { + "label": "Asia Pacific (Tokyo)", + "value": "ap-northeast-1" + }, + { + "label": "Canada (Central)", + "value": "ca-central-1" + }, + { + "label": "Europe (Frankfurt)", + "value": "eu-central-1" + }, + { + "label": "Europe (Ireland)", + "value": "eu-west-1" + }, + { + "label": "Europe (London)", + "value": "eu-west-2" + }, + { + "label": "Europe (Paris)", + "value": "eu-west-3" + }, + { + "label": "Europe (Stockholm)", + "value": "eu-north-1" + }, + { + "label": "Middle East (Bahrain)", + "value": "me-south-1" + }, + { + "label": "South America (São Paulo)", + "value": "sa-east-1" + } + ] +} diff --git a/mock_data/LMS_API.customer.json b/mock_data/LMS_API.customer.json index f36258fa6..554547e7a 100644 --- a/mock_data/LMS_API.customer.json +++ b/mock_data/LMS_API.customer.json @@ -1,73 +1,52 @@ { - "id": "customer-guid-1", - "name": "customer name", - "customer_hubspot_id": "guid", - "stripe_customer_object": { - "balance": 0, - "delinquent": true, - "discount": {}, - "id": "string", - "sources": { - "data": [ - { - "id": "card_1GFpVMDzYFZjJeDRi5I0el9T", - "object": "card", - "address_city": null, - "address_country": null, - "address_line1": null, - "address_line1_check": null, - "address_line2": null, - "address_state": null, - "address_zip": "80209", - "address_zip_check": null, - "brand": "Visa", - "country": "US", - "customer": "cus_GnQM9k8y55BtWs", - "cvc_check": null, - "dynamic_last4": null, - "exp_month": 8, - "exp_year": 2021, - "fingerprint": "ICvbhEovZZAH4OJf", - "funding": "credit", - "last4": "4242", - "metadata": {}, - "name": "Test Name", - "tokenization_method": null - } - ] - }, - "subscriptions": { - "data": [ - { - "billing_cycle_anchor": 0, - "billing_thresholds": {}, - "cancel_at": 0, - "cancel_at_period_end": true, - "canceled_at": 0, - "created": 0, - "current_period_end": 0, - "current_period_start": 0, - "customer": "string", - "default_payment_method": "string", - "default_source": "string", - "discount": {}, - "ended_at": 0, - "id": "string", - "items": { - "data": [ - {} - ] - }, - "latest_invoice": {}, - "metadata": {}, - "next_pending_invoice_item_invoice": 0, - "plan": "string", - "quantity": 0, - "schedule": {}, - "start_date": 0, - "status": "active" - } - ] + "statusCode": 200, + "body": { + "id": "customer-guid-1", + "name": "customer name", + "customer_hubspot_id": "guid", + "stripe_customer_object": { + "balance": 0, + "delinquent": true, + "discount": {}, + "id": "string", + "sources": { + "data": [ + + ] + }, + "subscriptions": { + "data": [ + { + "billing_cycle_anchor": 0, + "billing_thresholds": {}, + "cancel_at": 0, + "cancel_at_period_end": true, + "canceled_at": 0, + "created": 0, + "current_period_end": 0, + "current_period_start": 0, + "customer": "string", + "default_payment_method": "string", + "default_source": "string", + "discount": {}, + "ended_at": 0, + "id": "string", + "items": { + "data": [ + {} + ] + }, + "latest_invoice": {}, + "metadata": {}, + "next_pending_invoice_item_invoice": 0, + "plan": "string", + "quantity": 0, + "schedule": {}, + "start_date": 0, + "status": "active" + } + ] + } } } } diff --git a/mock_data/LMS_API.customer_users.json b/mock_data/LMS_API.customer_users.json index ab97e24a1..46fab6013 100644 --- a/mock_data/LMS_API.customer_users.json +++ b/mock_data/LMS_API.customer_users.json @@ -1,37 +1,40 @@ -[ - { - "user_id": "user-guid-1", - "firstname": "First1", - "lastname": "Last1", - "email": "user1@harperdb.io", - "customer_id": "customer-id-guid" - }, - { - "user_id": "user-guid-2", - "firstname": "First2", - "lastname": "Last2", - "email": "user2@harperdb.io", - "customer_id": "customer-id-guid" - }, - { - "user_id": "user-guid-3", - "firstname": "First3", - "lastname": "Last3", - "email": "user3@harperdb.io", - "customer_id": "customer-id-guid" - }, - { - "user_id": "user-guid-4", - "firstname": "First4", - "lastname": "Last4", - "email": "user4@harperdb.io", - "customer_id": "customer-id-guid" - }, - { - "user_id": "user-guid-5", - "firstname": "First5", - "lastname": "Last5", - "email": "user5@harperdb.io", - "customer_id": "customer-id-guid" - } -] +{ + "statusCode": 200, + "body": [ + { + "user_id": "user-guid-1", + "firstname": "First1", + "lastname": "Last1", + "email": "user1@harperdb.io", + "customer_id": "customer-id-guid" + }, + { + "user_id": "user-guid-2", + "firstname": "First2", + "lastname": "Last2", + "email": "user2@harperdb.io", + "customer_id": "customer-id-guid" + }, + { + "user_id": "user-guid-3", + "firstname": "First3", + "lastname": "Last3", + "email": "user3@harperdb.io", + "customer_id": "customer-id-guid" + }, + { + "user_id": "user-guid-4", + "firstname": "First4", + "lastname": "Last4", + "email": "user4@harperdb.io", + "customer_id": "customer-id-guid" + }, + { + "user_id": "user-guid-5", + "firstname": "First5", + "lastname": "Last5", + "email": "user5@harperdb.io", + "customer_id": "customer-id-guid" + } + ] +} diff --git a/mock_data/LMS_API.instances.json b/mock_data/LMS_API.instances.json index 5a56de647..316838a5b 100644 --- a/mock_data/LMS_API.instances.json +++ b/mock_data/LMS_API.instances.json @@ -1,154 +1,157 @@ -[ - { - "id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", - "instance_name": "3f6c9e35f6a84fb7b09f8b3e7d4e20df", - "host": "localhost", - "port": 9925, - "cluster_port": 12345, - "is_ssl": false, - "is_local": true, - "status": "ready", - "instance_region": null, - "domain_name": null, - "elastic_ip_address": null, - "customer_id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", - "stripe_product_id": "prod_GoB3yLzygPeGMu", - "stripe_plan_id": "plan_GoB4aP5U6PuR6u", - "stripe_storage_product_id": null, - "stripe_storage_plan_id": null, - "storage_qty_gb": null - }, - { - "id": "4f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20de", - "instance_name": "ClusterDemoA1", - "host": "ClusterDemoA1", - "port": 9925, - "cluster_port": 12345, - "is_ssl": false, - "is_local": false, - "status": "ready", - "instance_region": "us-east-1", - "domain_name": "4f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20de.cloud.harperdb.io", - "elastic_ip_address": "172.168.2.12", - "customer_id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", - "stripe_product_id": "prod_Gh1XXQx6J8YaJl", - "stripe_plan_id": "plan_GoB54KxG5JRkwm", - "stripe_storage_product_id": "prod_GoUJnVwOYvTjU9", - "stripe_storage_plan_id": "plan_GoUmLEBX2KIiaF", - "storage_qty_gb": 10 - }, - { - "id": "cee98941-8975-44d6-88e6-9a1a4fa02b25", - "instance_name": "ClusterDemoA2", - "host": "ClusterDemoA2", - "port": 9925, - "cluster_port": 12345, - "is_ssl": false, - "is_local": false, - "status": "ready", - "instance_region": "us-east-1", - "domain_name": "cee98941-8975-44d6-88e6-9a1a4fa02b25.cloud.harperdb.io", - "elastic_ip_address": "172.168.2.12", - "customer_id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", - "stripe_product_id": "prod_Gh1XXQx6J8YaJl", - "stripe_plan_id": "2GB_Beta", - "stripe_storage_product_id": "prod_GoUJnVwOYvTjU9", - "stripe_storage_plan_id": "plan_GoUmLEBX2KIiaF", - "storage_qty_gb": 100 - }, - { - "id": "a32b2464-b5ca-4b9d-a10f-c6da23a7dcbe", - "instance_name": "ClusterDemoA3", - "host": "ClusterDemoA3", - "port": 9925, - "cluster_port": 12345, - "is_ssl": false, - "is_local": false, - "status": "ready", - "instance_region": "us-east-1", - "domain_name": "a32b2464-b5ca-4b9d-a10f-c6da23a7dcbe.cloud.harperdb.io", - "elastic_ip_address": "172.168.2.12", - "customer_id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", - "stripe_product_id": "prod_Gh1XXQx6J8YaJl", - "stripe_plan_id": "4GB_Beta", - "stripe_storage_product_id": "prod_GoUJnVwOYvTjU9", - "stripe_storage_plan_id": "plan_GoUmLEBX2KIiaF", - "storage_qty_gb": 250 - }, - { - "id": "7a60b51d-7983-4659-a34b-cfb578a4db54", - "instance_name": "ClusterDemoA4", - "host": "ClusterDemoA4", - "port": 9925, - "cluster_port": 12345, - "is_ssl": false, - "is_local": false, - "status": "ready", - "instance_region": "us-east-1", - "domain_name": "7a60b51d-7983-4659-a34b-cfb578a4db54.cloud.harperdb.io", - "elastic_ip_address": "172.168.2.12", - "customer_id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", - "stripe_product_id": "prod_Gh1XXQx6J8YaJl", - "stripe_plan_id": "8GB_Beta", - "stripe_storage_product_id": "prod_GoUJnVwOYvTjU9", - "stripe_storage_plan_id": "plan_GoUmLEBX2KIiaF", - "storage_qty_gb": 500 - }, - { - "id": "f0d29404-9fb1-4d66-ab98-41f05e1ac665", - "instance_name": "ClusterDemoA5", - "host": "ClusterDemoA5", - "port": 9925, - "cluster_port": 12345, - "is_ssl": false, - "is_local": false, - "status": "ready", - "instance_region": "us-east-1", - "domain_name": "f0d29404-9fb1-4d66-ab98-41f05e1ac665.cloud.harperdb.io", - "elastic_ip_address": "172.168.2.12", - "customer_id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", - "stripe_product_id": "prod_Gh1XXQx6J8YaJl", - "stripe_plan_id": "4GB_Beta", - "stripe_storage_product_id": "prod_GoUJnVwOYvTjU9", - "stripe_storage_plan_id": "plan_GoUmLEBX2KIiaF", - "storage_qty_gb": 1000 - }, - { - "id": "ccf2b958-0b0e-4bf6-a81d-6abee1e4eec5", - "instance_name": "ClusterDemoA6", - "host": "ClusterDemoA6", - "port": 9925, - "cluster_port": 12345, - "is_ssl": false, - "is_local": false, - "status": "ready", - "instance_region": "us-east-1", - "domain_name": "ccf2b958-0b0e-4bf6-a81d-6abee1e4eec5.cloud.harperdb.io", - "elastic_ip_address": "172.168.2.12", - "customer_id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", - "stripe_product_id": "prod_Gh1XXQx6J8YaJl", - "stripe_plan_id": "2GB_Beta", - "stripe_storage_product_id": "prod_GoUJnVwOYvTjU9", - "stripe_storage_plan_id": "plan_GoUmLEBX2KIiaF", - "storage_qty_gb": 500 - }, - { - "id": "8291c224-6911-4155-b5a9-34559e5a45ff", - "instance_name": "ClusterDemoA7", - "host": "ClusterDemoA7", - "port": 9925, - "cluster_port": 12345, - "is_ssl": false, - "is_local": false, - "status": "ready", - "instance_region": "us-east-1", - "domain_name": "8291c224-6911-4155-b5a9-34559e5a45ff.cloud.harperdb.io", - "elastic_ip_address": "172.168.2.12", - "customer_id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", - "stripe_product_id": "prod_Gh1XXQx6J8YaJl", - "stripe_plan_id": "plan_GoB54KxG5JRkwm", - "stripe_storage_product_id": "prod_GoUJnVwOYvTjU9", - "stripe_storage_plan_id": "plan_GoUmLEBX2KIiaF", - "storage_qty_gb": 250 - } -] +{ + "statusCode":200, + "body":[ + { + "id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", + "instance_name": "3f6c9e35f6a84fb7b09f8b3e7d4e20df", + "host": "localhost", + "port": 9925, + "cluster_port": 12345, + "is_ssl": false, + "is_local": true, + "status": "ready", + "instance_region": null, + "domain_name": null, + "elastic_ip_address": null, + "customer_id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", + "stripe_product_id": "prod_GoB3yLzygPeGMu", + "stripe_plan_id": "plan_GoB4aP5U6PuR6u", + "stripe_storage_product_id": null, + "stripe_storage_plan_id": null, + "storage_qty_gb": null + }, + { + "id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20de", + "instance_name": "ClusterDemoA1", + "host": "ClusterDemoA1", + "port": 9925, + "cluster_port": 12345, + "is_ssl": false, + "is_local": false, + "status": "ready", + "instance_region": "us-east-1", + "domain_name": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20de.cloud.harperdb.io", + "elastic_ip_address": "172.168.2.12", + "customer_id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", + "stripe_product_id": "prod_Gh1XXQx6J8YaJl", + "stripe_plan_id": "plan_GoB54KxG5JRkwm", + "stripe_storage_product_id": "prod_GoUJnVwOYvTjU9", + "stripe_storage_plan_id": "plan_GoUmLEBX2KIiaF", + "storage_qty_gb": 10 + }, + { + "id": "cee98941-8975-44d6-88e6-9a1a4fa02b25", + "instance_name": "ClusterDemoA2", + "host": "ClusterDemoA2", + "port": 9925, + "cluster_port": 12345, + "is_ssl": false, + "is_local": false, + "status": "ready", + "instance_region": "us-east-1", + "domain_name": "cee98941-8975-44d6-88e6-9a1a4fa02b25.cloud.harperdb.io", + "elastic_ip_address": "172.168.2.12", + "customer_id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", + "stripe_product_id": "prod_Gh1XXQx6J8YaJl", + "stripe_plan_id": "2GB_Beta", + "stripe_storage_product_id": "prod_GoUJnVwOYvTjU9", + "stripe_storage_plan_id": "plan_GoUmLEBX2KIiaF", + "storage_qty_gb": 100 + }, + { + "id": "a32b2464-b5ca-4b9d-a10f-c6da23a7dcbe", + "instance_name": "ClusterDemoA3", + "host": "ClusterDemoA3", + "port": 9925, + "cluster_port": 12345, + "is_ssl": false, + "is_local": false, + "status": "ready", + "instance_region": "us-east-1", + "domain_name": "a32b2464-b5ca-4b9d-a10f-c6da23a7dcbe.cloud.harperdb.io", + "elastic_ip_address": "172.168.2.12", + "customer_id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", + "stripe_product_id": "prod_Gh1XXQx6J8YaJl", + "stripe_plan_id": "4GB_Beta", + "stripe_storage_product_id": "prod_GoUJnVwOYvTjU9", + "stripe_storage_plan_id": "plan_GoUmLEBX2KIiaF", + "storage_qty_gb": 250 + }, + { + "id": "7a60b51d-7983-4659-a34b-cfb578a4db54", + "instance_name": "ClusterDemoA4", + "host": "ClusterDemoA4", + "port": 9925, + "cluster_port": 12345, + "is_ssl": false, + "is_local": false, + "status": "ready", + "instance_region": "us-east-1", + "domain_name": "7a60b51d-7983-4659-a34b-cfb578a4db54.cloud.harperdb.io", + "elastic_ip_address": "172.168.2.12", + "customer_id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", + "stripe_product_id": "prod_Gh1XXQx6J8YaJl", + "stripe_plan_id": "8GB_Beta", + "stripe_storage_product_id": "prod_GoUJnVwOYvTjU9", + "stripe_storage_plan_id": "plan_GoUmLEBX2KIiaF", + "storage_qty_gb": 500 + }, + { + "id": "f0d29404-9fb1-4d66-ab98-41f05e1ac665", + "instance_name": "ClusterDemoA5", + "host": "ClusterDemoA5", + "port": 9925, + "cluster_port": 12345, + "is_ssl": false, + "is_local": false, + "status": "ready", + "instance_region": "us-east-1", + "domain_name": "f0d29404-9fb1-4d66-ab98-41f05e1ac665.cloud.harperdb.io", + "elastic_ip_address": "172.168.2.12", + "customer_id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", + "stripe_product_id": "prod_Gh1XXQx6J8YaJl", + "stripe_plan_id": "4GB_Beta", + "stripe_storage_product_id": "prod_GoUJnVwOYvTjU9", + "stripe_storage_plan_id": "plan_GoUmLEBX2KIiaF", + "storage_qty_gb": 1000 + }, + { + "id": "ccf2b958-0b0e-4bf6-a81d-6abee1e4eec5", + "instance_name": "ClusterDemoA6", + "host": "ClusterDemoA6", + "port": 9925, + "cluster_port": 12345, + "is_ssl": false, + "is_local": false, + "status": "ready", + "instance_region": "us-east-1", + "domain_name": "ccf2b958-0b0e-4bf6-a81d-6abee1e4eec5.cloud.harperdb.io", + "elastic_ip_address": "172.168.2.12", + "customer_id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", + "stripe_product_id": "prod_Gh1XXQx6J8YaJl", + "stripe_plan_id": "2GB_Beta", + "stripe_storage_product_id": "prod_GoUJnVwOYvTjU9", + "stripe_storage_plan_id": "plan_GoUmLEBX2KIiaF", + "storage_qty_gb": 500 + }, + { + "id": "8291c224-6911-4155-b5a9-34559e5a45ff", + "instance_name": "ClusterDemoA7", + "host": "ClusterDemoA7", + "port": 9925, + "cluster_port": 12345, + "is_ssl": false, + "is_local": false, + "status": "ready", + "instance_region": "us-east-1", + "domain_name": "8291c224-6911-4155-b5a9-34559e5a45ff.cloud.harperdb.io", + "elastic_ip_address": "172.168.2.12", + "customer_id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", + "stripe_product_id": "prod_Gh1XXQx6J8YaJl", + "stripe_plan_id": "plan_GoB54KxG5JRkwm", + "stripe_storage_product_id": "prod_GoUJnVwOYvTjU9", + "stripe_storage_plan_id": "plan_GoUmLEBX2KIiaF", + "storage_qty_gb": 250 + } + ] +} diff --git a/mock_data/LMS_API.invoices.json b/mock_data/LMS_API.invoices.json index a797d8f1c..6a373620f 100644 --- a/mock_data/LMS_API.invoices.json +++ b/mock_data/LMS_API.invoices.json @@ -1,46 +1,10085 @@ -[ - { - "id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20d3", - "customer_id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", - "invoice_date": 1582577209446, - "billing_period_start": 1582577109446, - "billing_period_end": 1582577209446, - "total": 3998, - "items": [ - { - "instance_id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20de", - "instance_name": "instance 1", - "stripe_product_id": "product_id", - "invoice_amount": 1999 - }, - { - "instance_id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", - "instance_name": "instance 2", - "stripe_product_id": "product_id", - "invoice_amount": 1999 - } - ] - }, - { - "id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", - "customer_id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", - "invoice_date": 1582577109446, - "billing_period_start": 1582577009446, - "billing_period_end": 1582577109446, - "total": 3998, - "items": [ - { - "instance_id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20de", - "instance_name": "instance 1", - "stripe_product_id": "product_id", - "invoice_amount": 1999 - }, - { - "instance_id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", - "instance_name": "instance 2", - "stripe_product_id": "product_id", - "invoice_amount": 1999 - } - ] - } -] +{ + "statusCode": 200, + "body": [ + { + "id": "in_1GGqnBDzYFZjJeDRx7SGITwH", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_create", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582826941, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_GoTk70y3SRjO2wVgHQhMU55kZf1uKUk", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_GoTk70y3SRjO2wVgHQhMU55kZf1uKUk/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GGqnBDzYFZjJeDRJ8paPOcz", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1585332241, + "start": 1582826941 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_GoTksFs4THRcSh", + "subscription_item": "si_GoTkVNZ9MrHTHv", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GGqnBDzYFZjJeDRx7SGITwH/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0080", + "paid": true, + "payment_intent": null, + "period_end": 1582826941, + "period_start": 1582826941, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582826941, + "marked_uncollectible_at": null, + "paid_at": 1582826941, + "voided_at": null + }, + "subscription": "sub_GoTksFs4THRcSh", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582826943 + }, + { + "id": "in_1GGqmXDzYFZjJeDR3oISLJ2d", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_create", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582826901, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_GoTkzHcYqWqQHkekJbFLu4j2gaiHeFr", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_GoTkzHcYqWqQHkekJbFLu4j2gaiHeFr/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GGqmXDzYFZjJeDRHavUMOQg", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1585332201, + "start": 1582826901 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_GoTkflQd0tjsLa", + "subscription_item": "si_GoTk80jQCjowZz", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GGqmXDzYFZjJeDR3oISLJ2d/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0079", + "paid": true, + "payment_intent": null, + "period_end": 1582826901, + "period_start": 1582826901, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582826901, + "marked_uncollectible_at": null, + "paid_at": 1582826901, + "voided_at": null + }, + "subscription": "sub_GoTkflQd0tjsLa", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582826903 + }, + { + "id": "in_1GGqlaDzYFZjJeDRlJvpANX4", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_create", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582826842, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_GoTjrbQxwCdVdDTGrx3s63mB6uw3ru3", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_GoTjrbQxwCdVdDTGrx3s63mB6uw3ru3/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GGqlaDzYFZjJeDRvNtovpsd", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1585332141, + "start": 1582826841 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_GoTjI4THKEGfgp", + "subscription_item": "si_GoTj5A2mt5B1nb", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GGqlaDzYFZjJeDRlJvpANX4/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0078", + "paid": true, + "payment_intent": null, + "period_end": 1582826841, + "period_start": 1582826841, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582826842, + "marked_uncollectible_at": null, + "paid_at": 1582826842, + "voided_at": null + }, + "subscription": "sub_GoTjI4THKEGfgp", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582826843 + }, + { + "id": "in_1GGqkMDzYFZjJeDRFTxnrcXK", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_create", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582826766, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_GoThtFHfQQmrDUXPrjfTtPGvTrMsRlJ", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_GoThtFHfQQmrDUXPrjfTtPGvTrMsRlJ/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GGqkMDzYFZjJeDRhSK8KyQu", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1585332066, + "start": 1582826766 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_GoThbxMRcr9zhX", + "subscription_item": "si_GoThvYnKN8gEjZ", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GGqkMDzYFZjJeDRFTxnrcXK/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0077", + "paid": true, + "payment_intent": null, + "period_end": 1582826766, + "period_start": 1582826766, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582826766, + "marked_uncollectible_at": null, + "paid_at": 1582826767, + "voided_at": null + }, + "subscription": "sub_GoThbxMRcr9zhX", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582826769 + }, + { + "id": "in_1GGUXvDzYFZjJeDR5tTxS57f", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_create", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582741427, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_dIHeo1q793bHQmi4T7tVUjlKsu", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_dIHeo1q793bHQmi4T7tVUjlKsu/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GGUXvDzYFZjJeDR9NKfd0Rx", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1585246726, + "start": 1582741426 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_Go6lcvvsPhlmwz", + "subscription_item": "si_Go6lsEjhe5MSk1", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GGUXvDzYFZjJeDR5tTxS57f/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0076", + "paid": true, + "payment_intent": null, + "period_end": 1582741426, + "period_start": 1582741426, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582741427, + "marked_uncollectible_at": null, + "paid_at": 1582741427, + "voided_at": null + }, + "subscription": "sub_Go6lcvvsPhlmwz", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582741429 + }, + { + "id": "in_1GGRhcDzYFZjJeDRB3VP8Pw5", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_create", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582730496, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_TMKea86ajpCYTBTMOBXUCApUXK", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_TMKea86ajpCYTBTMOBXUCApUXK/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GGRhcDzYFZjJeDRQcM7uBWp", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1585235796, + "start": 1582730496 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_Go3pe9VANDiTc9", + "subscription_item": "si_Go3puUNx6ZbEpG", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GGRhcDzYFZjJeDRB3VP8Pw5/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0075", + "paid": true, + "payment_intent": null, + "period_end": 1582730496, + "period_start": 1582730496, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582730496, + "marked_uncollectible_at": null, + "paid_at": 1582730496, + "voided_at": null + }, + "subscription": "sub_Go3pe9VANDiTc9", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582730498 + }, + { + "id": "in_1GGRh3DzYFZjJeDR1SMMBe1W", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_create", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582730461, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_IW6cv8I5wBcd55QvaebrbegDIE", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_IW6cv8I5wBcd55QvaebrbegDIE/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GGRh3DzYFZjJeDR3Yk2yD8b", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1585235761, + "start": 1582730461 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_Go3oUx2kHM69KA", + "subscription_item": "si_Go3oyUkycK9FqW", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GGRh3DzYFZjJeDR1SMMBe1W/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0074", + "paid": true, + "payment_intent": null, + "period_end": 1582730461, + "period_start": 1582730461, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582730461, + "marked_uncollectible_at": null, + "paid_at": 1582730461, + "voided_at": null + }, + "subscription": "sub_Go3oUx2kHM69KA", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582730463 + }, + { + "id": "in_1GGRedDzYFZjJeDRg8IjDYDy", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_create", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582730311, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_5CX8ZwjgraZ662YKX7U29zgDil", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_5CX8ZwjgraZ662YKX7U29zgDil/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GGRedDzYFZjJeDR1IMR1pkN", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1585235611, + "start": 1582730311 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_Go3m8tXxiKT4Ty", + "subscription_item": "si_Go3mGRTuf5rWZx", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GGRedDzYFZjJeDRg8IjDYDy/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0073", + "paid": true, + "payment_intent": null, + "period_end": 1582730311, + "period_start": 1582730311, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582730311, + "marked_uncollectible_at": null, + "paid_at": 1582730311, + "voided_at": null + }, + "subscription": "sub_Go3m8tXxiKT4Ty", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582730313 + }, + { + "id": "in_1GGRdqDzYFZjJeDRjUzkk1Eh", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_create", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582730262, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_XD14M63eK2jYVSx9qixP7AAvjz", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_XD14M63eK2jYVSx9qixP7AAvjz/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GGRdqDzYFZjJeDRzlZXvPii", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1585235562, + "start": 1582730262 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_Go3lauL903bwh8", + "subscription_item": "si_Go3lnIuDonyrgf", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GGRdqDzYFZjJeDRjUzkk1Eh/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0072", + "paid": true, + "payment_intent": null, + "period_end": 1582730262, + "period_start": 1582730262, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582730262, + "marked_uncollectible_at": null, + "paid_at": 1582730262, + "voided_at": null + }, + "subscription": "sub_Go3lauL903bwh8", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582730264 + }, + { + "id": "in_1GGRdPDzYFZjJeDRDvITFS77", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_create", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582730235, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_tSxW2jSIPe6fYX2oBdCoX34iiT", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_tSxW2jSIPe6fYX2oBdCoX34iiT/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GGRdPDzYFZjJeDRwc55ihMT", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1585235535, + "start": 1582730235 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_Go3k87xpcBIv6x", + "subscription_item": "si_Go3kUzIVzyRrmr", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GGRdPDzYFZjJeDRDvITFS77/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0071", + "paid": true, + "payment_intent": null, + "period_end": 1582730235, + "period_start": 1582730235, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582730235, + "marked_uncollectible_at": null, + "paid_at": 1582730235, + "voided_at": null + }, + "subscription": "sub_Go3k87xpcBIv6x", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582730238 + }, + { + "id": "in_1GGRbyDzYFZjJeDRQNg9dq7u", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_create", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582730146, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_A8LoHr9sgzlNNNAlAH3KwHaBqY", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_A8LoHr9sgzlNNNAlAH3KwHaBqY/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GGRbyDzYFZjJeDRUyzLvc17", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1585235446, + "start": 1582730146 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_Go3jZHcnyeTkW6", + "subscription_item": "si_Go3jmqKRgLu2qd", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GGRbyDzYFZjJeDRQNg9dq7u/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0070", + "paid": true, + "payment_intent": null, + "period_end": 1582730146, + "period_start": 1582730146, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582730146, + "marked_uncollectible_at": null, + "paid_at": 1582730146, + "voided_at": null + }, + "subscription": "sub_Go3jZHcnyeTkW6", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582730148 + }, + { + "id": "in_1GGRakDzYFZjJeDRQ7G5HBqs", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_create", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582730070, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_rUn371g8tF9YXRisLvb8uSKtT4", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_rUn371g8tF9YXRisLvb8uSKtT4/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GGRakDzYFZjJeDRvsYSUAsD", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1585235370, + "start": 1582730070 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_Go3iaBE7lzFdap", + "subscription_item": "si_Go3i9JPgQm0QHM", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GGRakDzYFZjJeDRQ7G5HBqs/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0069", + "paid": true, + "payment_intent": null, + "period_end": 1582730070, + "period_start": 1582730070, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582730070, + "marked_uncollectible_at": null, + "paid_at": 1582730070, + "voided_at": null + }, + "subscription": "sub_Go3iaBE7lzFdap", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582730072 + }, + { + "id": "in_1GGRZVDzYFZjJeDRTplM7NAC", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_create", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582729993, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_rAwB48WWYBazPysFu8Ll6zyQy5", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_rAwB48WWYBazPysFu8Ll6zyQy5/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GGRZVDzYFZjJeDR3ktOgruP", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1585235293, + "start": 1582729993 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_Go3gSlNmpIJKdQ", + "subscription_item": "si_Go3gZm51HuGkDV", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GGRZVDzYFZjJeDRTplM7NAC/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0068", + "paid": true, + "payment_intent": null, + "period_end": 1582729993, + "period_start": 1582729993, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582729993, + "marked_uncollectible_at": null, + "paid_at": 1582729993, + "voided_at": null + }, + "subscription": "sub_Go3gSlNmpIJKdQ", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582729995 + }, + { + "id": "in_1GGRYMDzYFZjJeDRfe9bBDKN", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_create", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582729922, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_davaEi03DD3tAMSKNvrBhMSOzq", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_davaEi03DD3tAMSKNvrBhMSOzq/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GGRYMDzYFZjJeDRgAv6vS9y", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1585235222, + "start": 1582729922 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_Go3fRflUF6FFzj", + "subscription_item": "si_Go3f9Tx2V5uW02", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GGRYMDzYFZjJeDRfe9bBDKN/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0067", + "paid": true, + "payment_intent": null, + "period_end": 1582729922, + "period_start": 1582729922, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582729922, + "marked_uncollectible_at": null, + "paid_at": 1582729922, + "voided_at": null + }, + "subscription": "sub_Go3fRflUF6FFzj", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582729923 + }, + { + "id": "in_1GGREIDzYFZjJeDREO2HYOQK", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_create", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582728678, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_JP5cIekfQXgWFNrl3oXzADibKX", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_JP5cIekfQXgWFNrl3oXzADibKX/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GGREIDzYFZjJeDRpvqByyvV", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1585233978, + "start": 1582728678 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_Go3KLVABhAeYvf", + "subscription_item": "si_Go3K5vNTBl4ghy", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GGREIDzYFZjJeDREO2HYOQK/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0066", + "paid": true, + "payment_intent": null, + "period_end": 1582728678, + "period_start": 1582728678, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582728678, + "marked_uncollectible_at": null, + "paid_at": 1582728678, + "voided_at": null + }, + "subscription": "sub_Go3KLVABhAeYvf", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582728680 + }, + { + "id": "in_1GGRCSDzYFZjJeDRkuyikBAT", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_create", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582728564, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_nam6PoBRBeEo8mskCGzR0JZCku", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_nam6PoBRBeEo8mskCGzR0JZCku/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GGRCSDzYFZjJeDRFXvl9OFM", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1585233864, + "start": 1582728564 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_Go3JR9FNrRdafq", + "subscription_item": "si_Go3JZiFdlAjzK3", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GGRCSDzYFZjJeDRkuyikBAT/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0065", + "paid": true, + "payment_intent": null, + "period_end": 1582728564, + "period_start": 1582728564, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582728564, + "marked_uncollectible_at": null, + "paid_at": 1582728564, + "voided_at": null + }, + "subscription": "sub_Go3JR9FNrRdafq", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582728565 + }, + { + "id": "in_1GGR3vDzYFZjJeDRlF42G6IR", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_cycle", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582728035, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_eRxIalXOGzgpKKegjG9IuYphrq", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_eRxIalXOGzgpKKegjG9IuYphrq/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GGR3vDzYFZjJeDR7RjNf4Qv", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1582727735, + "start": 1582727611 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_Go33Kf2Dpdf9G8", + "subscription_item": "si_Go33jlRcVhNkWX", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GGR3vDzYFZjJeDRlF42G6IR/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0064", + "paid": true, + "payment_intent": null, + "period_end": 1582728035, + "period_start": 1582727611, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582732514, + "marked_uncollectible_at": null, + "paid_at": 1582732514, + "voided_at": null + }, + "subscription": "sub_Go33Kf2Dpdf9G8", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582728036 + }, + { + "id": "in_1GGR3hDzYFZjJeDRWZabRdsr", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_cycle", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582728021, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_hhv5tbooD8svt325rcoirabRhD", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_hhv5tbooD8svt325rcoirabRhD/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GGR3hDzYFZjJeDR4Zvq12Im", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1582727721, + "start": 1582727764 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_Go35RQKNHYncyR", + "subscription_item": "si_Go35hcEvlqj1xN", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GGR3hDzYFZjJeDRWZabRdsr/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0063", + "paid": true, + "payment_intent": null, + "period_end": 1582728021, + "period_start": 1582727764, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582732522, + "marked_uncollectible_at": null, + "paid_at": 1582732522, + "voided_at": null + }, + "subscription": "sub_Go35RQKNHYncyR", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582728022 + }, + { + "id": "in_1GGQzYDzYFZjJeDRjEyaLp4V", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_create", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582727764, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_Ttk3o3Z7qrqH1zptSzaKFRKzxL", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_Ttk3o3Z7qrqH1zptSzaKFRKzxL/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GGQzYDzYFZjJeDRM4Xh4y3Z", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1585233064, + "start": 1582727764 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_Go35RQKNHYncyR", + "subscription_item": "si_Go35hcEvlqj1xN", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GGQzYDzYFZjJeDRjEyaLp4V/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0062", + "paid": true, + "payment_intent": null, + "period_end": 1582727764, + "period_start": 1582727764, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582727764, + "marked_uncollectible_at": null, + "paid_at": 1582727765, + "voided_at": null + }, + "subscription": "sub_Go35RQKNHYncyR", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582727771 + }, + { + "id": "in_1GGQx5DzYFZjJeDRvBmZdCIL", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_create", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582727611, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_UzonlvvDv4jGfQT4nItGuRjaez", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_UzonlvvDv4jGfQT4nItGuRjaez/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GGQx5DzYFZjJeDRTKcMCD9K", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1585232911, + "start": 1582727611 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_Go33Kf2Dpdf9G8", + "subscription_item": "si_Go33jlRcVhNkWX", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GGQx5DzYFZjJeDRvBmZdCIL/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0061", + "paid": true, + "payment_intent": null, + "period_end": 1582727611, + "period_start": 1582727611, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582727611, + "marked_uncollectible_at": null, + "paid_at": 1582727612, + "voided_at": null + }, + "subscription": "sub_Go33Kf2Dpdf9G8", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582727614 + }, + { + "id": "in_1GFobQDzYFZjJeDRDEnRquSp", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_create", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582580196, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_MR0RbKsm5vazs23kyVUeGKA6q4", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_MR0RbKsm5vazs23kyVUeGKA6q4/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GFobQDzYFZjJeDRa30EKeAF", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1585085496, + "start": 1582580196 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_GnPQcLofZ66QHM", + "subscription_item": "si_GnPQ8oFy9uhxfL", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GFobQDzYFZjJeDRDEnRquSp/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0060", + "paid": true, + "payment_intent": null, + "period_end": 1582580196, + "period_start": 1582580196, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582580196, + "marked_uncollectible_at": null, + "paid_at": 1582580196, + "voided_at": null + }, + "subscription": "sub_GnPQcLofZ66QHM", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582580198 + }, + { + "id": "in_1GFoaiDzYFZjJeDR8ykvUsfU", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_create", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582580152, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_ksXujMpdJjxCcsRAh96TQd9udt", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_ksXujMpdJjxCcsRAh96TQd9udt/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GFoaiDzYFZjJeDRX3Y2TA71", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1585085452, + "start": 1582580152 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_GnPPzn3xfAStQ5", + "subscription_item": "si_GnPP3qzMZVyxKL", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GFoaiDzYFZjJeDR8ykvUsfU/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0059", + "paid": true, + "payment_intent": null, + "period_end": 1582580152, + "period_start": 1582580152, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582580152, + "marked_uncollectible_at": null, + "paid_at": 1582580152, + "voided_at": null + }, + "subscription": "sub_GnPPzn3xfAStQ5", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582580159 + }, + { + "id": "in_1GFo3NDzYFZjJeDRvNCXZDvK", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_create", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582578085, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_hDsp19NVymdL9AdMsd2YKh3MkX", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_hDsp19NVymdL9AdMsd2YKh3MkX/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GFo3NDzYFZjJeDRkJ1hsTAb", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1585083385, + "start": 1582578085 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_GnOr6WVuzCuXxL", + "subscription_item": "si_GnOrRV7K40EhjD", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GFo3NDzYFZjJeDRvNCXZDvK/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0058", + "paid": true, + "payment_intent": null, + "period_end": 1582578085, + "period_start": 1582578085, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582578085, + "marked_uncollectible_at": null, + "paid_at": 1582578085, + "voided_at": null + }, + "subscription": "sub_GnOr6WVuzCuXxL", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582578087 + }, + { + "id": "in_1GFml6DzYFZjJeDRruR40Gsu", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_create", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582573108, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_seha2gR7yab2JUzzojZ4EzvNnI", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_seha2gR7yab2JUzzojZ4EzvNnI/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GFml6DzYFZjJeDRvUzJrVmM", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1585078408, + "start": 1582573108 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_GnNWRqSyMj18TR", + "subscription_item": "si_GnNWrabXCKKktR", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GFml6DzYFZjJeDRruR40Gsu/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0057", + "paid": true, + "payment_intent": null, + "period_end": 1582573108, + "period_start": 1582573108, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582573108, + "marked_uncollectible_at": null, + "paid_at": 1582573108, + "voided_at": null + }, + "subscription": "sub_GnNWRqSyMj18TR", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582573117 + }, + { + "id": "in_1GFmklDzYFZjJeDRYSZ4bDnr", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_cycle", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582573087, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_ZPjm1cr4WYuc3LktxAwJKNSjML", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_ZPjm1cr4WYuc3LktxAwJKNSjML/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GFmklDzYFZjJeDRnTuaJMjI", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1582572787, + "start": 1582221488 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_GlqzemuJUub4J2", + "subscription_item": "si_GlqzFkjDInRmBN", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GFmklDzYFZjJeDRYSZ4bDnr/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0056", + "paid": true, + "payment_intent": null, + "period_end": 1582573087, + "period_start": 1582221488, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582577224, + "marked_uncollectible_at": null, + "paid_at": 1582577224, + "voided_at": null + }, + "subscription": "sub_GlqzemuJUub4J2", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582573088 + }, + { + "id": "in_1GFmkfDzYFZjJeDRHKwiTy4J", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_cycle", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582573081, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_E5wiQVGpD38cxo2f3I9nS0dqYk", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_E5wiQVGpD38cxo2f3I9nS0dqYk/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GFmkfDzYFZjJeDRiQCBI0Dw", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1582572781, + "start": 1582232582 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_Glty5lWiwwQsSI", + "subscription_item": "si_Glty8AuagWpgnc", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GFmkfDzYFZjJeDRHKwiTy4J/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0055", + "paid": true, + "payment_intent": null, + "period_end": 1582573081, + "period_start": 1582232582, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582577214, + "marked_uncollectible_at": null, + "paid_at": 1582577214, + "voided_at": null + }, + "subscription": "sub_Glty5lWiwwQsSI", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582573085 + }, + { + "id": "in_1GFmkZDzYFZjJeDRIJmX6B0W", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_cycle", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582573075, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_sxYNlA8I8OGn2lioxY7F8bmLap", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_sxYNlA8I8OGn2lioxY7F8bmLap/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GFmkZDzYFZjJeDRfKXQomry", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1582572775, + "start": 1582231098 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_GltZ2afPg5EAGw", + "subscription_item": "si_GltZpXZrKPJnT2", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GFmkZDzYFZjJeDRIJmX6B0W/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0054", + "paid": true, + "payment_intent": null, + "period_end": 1582573075, + "period_start": 1582231098, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582577205, + "marked_uncollectible_at": null, + "paid_at": 1582577205, + "voided_at": null + }, + "subscription": "sub_GltZ2afPg5EAGw", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582573077 + }, + { + "id": "in_1GFmkODzYFZjJeDRPmqf5yJF", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_cycle", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582573064, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_0zrzxZAJksyvR0RrlmmxciD0pf", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_0zrzxZAJksyvR0RrlmmxciD0pf/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GFmkODzYFZjJeDRbwWTJX0N", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1582572764, + "start": 1582571784 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_GnNA0FQ4tZcOPu", + "subscription_item": "si_GnNAgOIhfYKe44", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GFmkODzYFZjJeDRPmqf5yJF/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0053", + "paid": true, + "payment_intent": null, + "period_end": 1582573064, + "period_start": 1582571784, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582577203, + "marked_uncollectible_at": null, + "paid_at": 1582577203, + "voided_at": null + }, + "subscription": "sub_GnNA0FQ4tZcOPu", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582573065 + }, + { + "id": "in_1GFmkHDzYFZjJeDRO0nJCWjm", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_cycle", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582573057, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_snTXzinz9LfKAYoC6LV31avoHx", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_snTXzinz9LfKAYoC6LV31avoHx/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GFmkHDzYFZjJeDRgF7ntWKZ", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1582572757, + "start": 1582571856 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_GnNBB7LK4xUktO", + "subscription_item": "si_GnNBpAidcb3UF0", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GFmkHDzYFZjJeDRO0nJCWjm/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0052", + "paid": true, + "payment_intent": null, + "period_end": 1582573057, + "period_start": 1582571856, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582577210, + "marked_uncollectible_at": null, + "paid_at": 1582577210, + "voided_at": null + }, + "subscription": "sub_GnNBB7LK4xUktO", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582573059 + }, + { + "id": "in_1GFmk9DzYFZjJeDRa8O2XxdC", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_cycle", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582573049, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_XnATXkvzBlXj6ieMVuixXWwffJ", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_XnATXkvzBlXj6ieMVuixXWwffJ/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GFmk9DzYFZjJeDR47NL0Cpl", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1582572749, + "start": 1582572448 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_GnNLqCSha2X98Y", + "subscription_item": "si_GnNLn0nygBRNWP", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GFmk9DzYFZjJeDRa8O2XxdC/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0051", + "paid": true, + "payment_intent": null, + "period_end": 1582573049, + "period_start": 1582572448, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582577207, + "marked_uncollectible_at": null, + "paid_at": 1582577207, + "voided_at": null + }, + "subscription": "sub_GnNLqCSha2X98Y", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582573050 + }, + { + "id": "in_1GFmaTDzYFZjJeDRwOUFsXQY", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_create", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582572449, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_oxmuCDBPDlSOxvW52GxIaDWPKU", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_oxmuCDBPDlSOxvW52GxIaDWPKU/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GFmaTDzYFZjJeDRdlvZWImt", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1585077748, + "start": 1582572448 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_GnNLqCSha2X98Y", + "subscription_item": "si_GnNLn0nygBRNWP", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GFmaTDzYFZjJeDRwOUFsXQY/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0050", + "paid": true, + "payment_intent": null, + "period_end": 1582572448, + "period_start": 1582572448, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582572449, + "marked_uncollectible_at": null, + "paid_at": 1582572449, + "voided_at": null + }, + "subscription": "sub_GnNLqCSha2X98Y", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582572451 + }, + { + "id": "in_1GFmQuDzYFZjJeDR02FtxjNB", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_create", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582571856, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_XYXHFqGAyxuB7OO9egytLNFtPt", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_XYXHFqGAyxuB7OO9egytLNFtPt/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GFmQuDzYFZjJeDRdCbfMZAv", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1585077156, + "start": 1582571856 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_GnNBB7LK4xUktO", + "subscription_item": "si_GnNBpAidcb3UF0", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GFmQuDzYFZjJeDR02FtxjNB/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0049", + "paid": true, + "payment_intent": null, + "period_end": 1582571856, + "period_start": 1582571856, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582571856, + "marked_uncollectible_at": null, + "paid_at": 1582571856, + "voided_at": null + }, + "subscription": "sub_GnNBB7LK4xUktO", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582571860 + }, + { + "id": "in_1GFmPkDzYFZjJeDRBVmtKgYA", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_create", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582571784, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_gMrYXSjQviPzj8D9eTr8PNVFTI", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_gMrYXSjQviPzj8D9eTr8PNVFTI/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GFmPkDzYFZjJeDR2viZnco8", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1585077084, + "start": 1582571784 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_GnNA0FQ4tZcOPu", + "subscription_item": "si_GnNAgOIhfYKe44", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GFmPkDzYFZjJeDRBVmtKgYA/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0048", + "paid": true, + "payment_intent": null, + "period_end": 1582571784, + "period_start": 1582571784, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582571784, + "marked_uncollectible_at": null, + "paid_at": 1582571784, + "voided_at": null + }, + "subscription": "sub_GnNA0FQ4tZcOPu", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582571788 + }, + { + "id": "in_1GEMAkDzYFZjJeDRdveo8Zvc", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_create", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582232582, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_KHdRJOV6HiiN9cIxWO9xI2oPn0", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_KHdRJOV6HiiN9cIxWO9xI2oPn0/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GEMAkDzYFZjJeDR0L56OtvT", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1584737882, + "start": 1582232582 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_Glty5lWiwwQsSI", + "subscription_item": "si_Glty8AuagWpgnc", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GEMAkDzYFZjJeDRdveo8Zvc/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0047", + "paid": true, + "payment_intent": null, + "period_end": 1582232582, + "period_start": 1582232582, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582232582, + "marked_uncollectible_at": null, + "paid_at": 1582232582, + "voided_at": null + }, + "subscription": "sub_Glty5lWiwwQsSI", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582232585 + }, + { + "id": "in_1GELmoDzYFZjJeDRiQ0f9LTf", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_create", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582231098, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_h7Nqq4BQiN3iU9kIHOuHdrWA50", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_h7Nqq4BQiN3iU9kIHOuHdrWA50/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GELmoDzYFZjJeDRS22jW51c", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1584736398, + "start": 1582231098 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_GltZ2afPg5EAGw", + "subscription_item": "si_GltZpXZrKPJnT2", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GELmoDzYFZjJeDRiQ0f9LTf/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0046", + "paid": true, + "payment_intent": null, + "period_end": 1582231098, + "period_start": 1582231098, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582231098, + "marked_uncollectible_at": null, + "paid_at": 1582231098, + "voided_at": null + }, + "subscription": "sub_GltZ2afPg5EAGw", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582231100 + }, + { + "id": "in_1GELmZDzYFZjJeDRIMJKP653", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_cycle", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582231083, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_bm11IFs3R8nYqdSiYZIGJeILXV", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_bm11IFs3R8nYqdSiYZIGJeILXV/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GELmZDzYFZjJeDRgOlb73L4", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1582230783, + "start": 1582221576 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_Glr1ZZbxUwPiGV", + "subscription_item": "si_Glr1tSio3TnB2P", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GELmZDzYFZjJeDRIMJKP653/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0045", + "paid": true, + "payment_intent": null, + "period_end": 1582231083, + "period_start": 1582221576, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582235157, + "marked_uncollectible_at": null, + "paid_at": 1582235157, + "voided_at": null + }, + "subscription": "sub_Glr1ZZbxUwPiGV", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582231084 + }, + { + "id": "in_1GELmTDzYFZjJeDRBQhRxdtx", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_cycle", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582231077, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_B9pWYmoIeeKmOg4WMOZQD2X4QD", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_B9pWYmoIeeKmOg4WMOZQD2X4QD/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GELmTDzYFZjJeDR2l9WNp3E", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1582230777, + "start": 1582221643 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_Glr2PDoSBxuZ6f", + "subscription_item": "si_Glr2mD15sxjhmI", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GELmTDzYFZjJeDRBQhRxdtx/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0044", + "paid": true, + "payment_intent": null, + "period_end": 1582231077, + "period_start": 1582221643, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582235154, + "marked_uncollectible_at": null, + "paid_at": 1582235154, + "voided_at": null + }, + "subscription": "sub_Glr2PDoSBxuZ6f", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582231078 + }, + { + "id": "in_1GELmNDzYFZjJeDRSHi4fXyn", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_cycle", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582231071, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_kVNBKDE2AZK2hOkEcMLgPuNo3R", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_kVNBKDE2AZK2hOkEcMLgPuNo3R/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GELmNDzYFZjJeDRFXLy9k6D", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1582230771, + "start": 1582222981 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_GlrOBDgD24odPV", + "subscription_item": "si_GlrO0WKeQoaOMg", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GELmNDzYFZjJeDRSHi4fXyn/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0043", + "paid": true, + "payment_intent": null, + "period_end": 1582231071, + "period_start": 1582222981, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582235148, + "marked_uncollectible_at": null, + "paid_at": 1582235149, + "voided_at": null + }, + "subscription": "sub_GlrOBDgD24odPV", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582231072 + }, + { + "id": "in_1GELmHDzYFZjJeDRZKHxABp7", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_cycle", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582231065, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_Axk6Njz3WuluErq4h2WLQWgRkZ", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_Axk6Njz3WuluErq4h2WLQWgRkZ/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GELmHDzYFZjJeDR2M91LzCL", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1582230765, + "start": 1582223033 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_GlrPgb4E5Me9eJ", + "subscription_item": "si_GlrPKqr4j9CXhc", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GELmHDzYFZjJeDRZKHxABp7/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0042", + "paid": true, + "payment_intent": null, + "period_end": 1582231065, + "period_start": 1582223033, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582235158, + "marked_uncollectible_at": null, + "paid_at": 1582235158, + "voided_at": null + }, + "subscription": "sub_GlrPgb4E5Me9eJ", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582231066 + }, + { + "id": "in_1GELmCDzYFZjJeDRRkN45vOH", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_cycle", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582231060, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_ExDIXy3vV8wrXHzusokemvey17", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_ExDIXy3vV8wrXHzusokemvey17/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GELmCDzYFZjJeDR8bve8Zu6", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1582230760, + "start": 1582223252 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_GlrTxE9JkS0cAE", + "subscription_item": "si_GlrT0DHyfkMIHm", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GELmCDzYFZjJeDRRkN45vOH/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0041", + "paid": true, + "payment_intent": null, + "period_end": 1582231060, + "period_start": 1582223252, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582235147, + "marked_uncollectible_at": null, + "paid_at": 1582235147, + "voided_at": null + }, + "subscription": "sub_GlrTxE9JkS0cAE", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582231061 + }, + { + "id": "in_1GEJkGDzYFZjJeDRvKfc2XXe", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_create", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582223252, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_GZ4t6ysF37nDrdbEPFtWWgTY59", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_GZ4t6ysF37nDrdbEPFtWWgTY59/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GEJkGDzYFZjJeDRY9JPVVKs", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1584728552, + "start": 1582223252 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_GlrTxE9JkS0cAE", + "subscription_item": "si_GlrT0DHyfkMIHm", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GEJkGDzYFZjJeDRvKfc2XXe/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0040", + "paid": true, + "payment_intent": null, + "period_end": 1582223252, + "period_start": 1582223252, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582223252, + "marked_uncollectible_at": null, + "paid_at": 1582223252, + "voided_at": null + }, + "subscription": "sub_GlrTxE9JkS0cAE", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582223254 + }, + { + "id": "in_1GEJgjDzYFZjJeDRp2f2qqKU", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_create", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582223033, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_chhdpbNFgIeWZan8b1NLqKHXyA", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_chhdpbNFgIeWZan8b1NLqKHXyA/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GEJgjDzYFZjJeDR9120arSs", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1584728333, + "start": 1582223033 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_GlrPgb4E5Me9eJ", + "subscription_item": "si_GlrPKqr4j9CXhc", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GEJgjDzYFZjJeDRp2f2qqKU/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0039", + "paid": true, + "payment_intent": null, + "period_end": 1582223033, + "period_start": 1582223033, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582223033, + "marked_uncollectible_at": null, + "paid_at": 1582223033, + "voided_at": null + }, + "subscription": "sub_GlrPgb4E5Me9eJ", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582223038 + }, + { + "id": "in_1GEJfuDzYFZjJeDR2h8b9ehx", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_create", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582222982, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_baxuiFBRd705KVv7KEvgn7UmGx", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_baxuiFBRd705KVv7KEvgn7UmGx/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GEJfuDzYFZjJeDR1rZQ8TMp", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1584728281, + "start": 1582222981 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_GlrOBDgD24odPV", + "subscription_item": "si_GlrO0WKeQoaOMg", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GEJfuDzYFZjJeDR2h8b9ehx/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0038", + "paid": true, + "payment_intent": null, + "period_end": 1582222981, + "period_start": 1582222981, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582222982, + "marked_uncollectible_at": null, + "paid_at": 1582222982, + "voided_at": null + }, + "subscription": "sub_GlrOBDgD24odPV", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582222984 + }, + { + "id": "in_1GEJKKDzYFZjJeDR6SsO85nF", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_create", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582221644, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_AHdbLFJEP7z78Z6vTcMaDlAZZD", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_AHdbLFJEP7z78Z6vTcMaDlAZZD/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GEJKKDzYFZjJeDRhqwRAi8N", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1584726943, + "start": 1582221643 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_Glr2PDoSBxuZ6f", + "subscription_item": "si_Glr2mD15sxjhmI", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GEJKKDzYFZjJeDR6SsO85nF/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0037", + "paid": true, + "payment_intent": null, + "period_end": 1582221643, + "period_start": 1582221643, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582221644, + "marked_uncollectible_at": null, + "paid_at": 1582221644, + "voided_at": null + }, + "subscription": "sub_Glr2PDoSBxuZ6f", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582221645 + }, + { + "id": "in_1GEJJEDzYFZjJeDRctYAuagR", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_create", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582221576, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_FxBcgiM20IqF1RcPrKOVldjZ22", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_FxBcgiM20IqF1RcPrKOVldjZ22/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GEJJEDzYFZjJeDRGvC3oB2Y", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1584726876, + "start": 1582221576 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_Glr1ZZbxUwPiGV", + "subscription_item": "si_Glr1tSio3TnB2P", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GEJJEDzYFZjJeDRctYAuagR/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0036", + "paid": true, + "payment_intent": null, + "period_end": 1582221576, + "period_start": 1582221576, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582221576, + "marked_uncollectible_at": null, + "paid_at": 1582221576, + "voided_at": null + }, + "subscription": "sub_Glr1ZZbxUwPiGV", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582221578 + }, + { + "id": "in_1GEJJDDzYFZjJeDREaJEub1r", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_cycle", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582221575, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_Aek8XK16TeljdETsya55Deph5c", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_Aek8XK16TeljdETsya55Deph5c/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GEJJDDzYFZjJeDRgcFMBTlu", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1582221275, + "start": 1582138370 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_GlUeGWXnVwSjJH", + "subscription_item": "si_GlUe0gtM1vB8JB", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GEJJDDzYFZjJeDREaJEub1r/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0035", + "paid": true, + "payment_intent": null, + "period_end": 1582221575, + "period_start": 1582138370, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582225268, + "marked_uncollectible_at": null, + "paid_at": 1582225268, + "voided_at": null + }, + "subscription": "sub_GlUeGWXnVwSjJH", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582221577 + }, + { + "id": "in_1GEJHoDzYFZjJeDRi9J6mBcI", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_create", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582221488, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_C7WWa7DZeUoQpZ82mJs2MNBIq1", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_C7WWa7DZeUoQpZ82mJs2MNBIq1/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GEJHoDzYFZjJeDRrYTtcIoU", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1584726788, + "start": 1582221488 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_GlqzemuJUub4J2", + "subscription_item": "si_GlqzFkjDInRmBN", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GEJHoDzYFZjJeDRi9J6mBcI/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0034", + "paid": true, + "payment_intent": null, + "period_end": 1582221488, + "period_start": 1582221488, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582221488, + "marked_uncollectible_at": null, + "paid_at": 1582221488, + "voided_at": null + }, + "subscription": "sub_GlqzemuJUub4J2", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582221490 + }, + { + "id": "in_1GEJG5DzYFZjJeDRsuv72EiP", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_create", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582221381, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_VA6JSOLOEWPwqxanRL3Cn2ThFJ", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_VA6JSOLOEWPwqxanRL3Cn2ThFJ/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GEJG5DzYFZjJeDRh6pAxiqm", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1584726681, + "start": 1582221381 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_GlqyjH16RvI5op", + "subscription_item": "si_GlqyrYVz6CRHt8", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GEJG5DzYFZjJeDRsuv72EiP/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0033", + "paid": true, + "payment_intent": null, + "period_end": 1582221381, + "period_start": 1582221381, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582221381, + "marked_uncollectible_at": null, + "paid_at": 1582221381, + "voided_at": null + }, + "subscription": "sub_GlqyjH16RvI5op", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582221383 + }, + { + "id": "in_1GEJFmDzYFZjJeDRhzUSMki8", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_cycle", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582221362, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_o8wu4TDyXWBa5rSs6Xj8qwXn6W", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_o8wu4TDyXWBa5rSs6Xj8qwXn6W/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GEJFmDzYFZjJeDR2ammr3u2", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1582221062, + "start": 1582138445 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_GlUf9jU1du2sec", + "subscription_item": "si_GlUfAEkGHyeN3H", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GEJFmDzYFZjJeDRhzUSMki8/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0032", + "paid": true, + "payment_intent": null, + "period_end": 1582221362, + "period_start": 1582138445, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582225281, + "marked_uncollectible_at": null, + "paid_at": 1582225281, + "voided_at": null + }, + "subscription": "sub_GlUf9jU1du2sec", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582221364 + }, + { + "id": "in_1GEJFfDzYFZjJeDRoGejeFwH", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_cycle", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582221355, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_JOMHlNP1swNSmXct5rZNXrch27", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_JOMHlNP1swNSmXct5rZNXrch27/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GEJFfDzYFZjJeDRi1vgmna4", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1582221055, + "start": 1582138470 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_GlUgiX3ZLbSFWc", + "subscription_item": "si_GlUgyngaPGIGve", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GEJFfDzYFZjJeDRoGejeFwH/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0031", + "paid": true, + "payment_intent": null, + "period_end": 1582221355, + "period_start": 1582138470, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582225267, + "marked_uncollectible_at": null, + "paid_at": 1582225267, + "voided_at": null + }, + "subscription": "sub_GlUgiX3ZLbSFWc", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582221357 + }, + { + "id": "in_1GEJFZDzYFZjJeDRbYf5rRla", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_cycle", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582221348, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_xn6dWbOJWDYi5HTmSV7yhDf54D", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_xn6dWbOJWDYi5HTmSV7yhDf54D/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GEJFYDzYFZjJeDRtBUjs46p", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1582221049, + "start": 1582138558 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_GlUhSfsh4CvqKT", + "subscription_item": "si_GlUhRKEz1f6wu0", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GEJFZDzYFZjJeDRbYf5rRla/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0030", + "paid": true, + "payment_intent": null, + "period_end": 1582221348, + "period_start": 1582138558, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582225284, + "marked_uncollectible_at": null, + "paid_at": 1582225284, + "voided_at": null + }, + "subscription": "sub_GlUhSfsh4CvqKT", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582221350 + }, + { + "id": "in_1GEJFRDzYFZjJeDR8Zw42zWU", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_cycle", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582221341, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_VRzdnJzcchGHTclDylbhDpdXJM", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_VRzdnJzcchGHTclDylbhDpdXJM/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GEJFRDzYFZjJeDRS9d5fUpf", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1582221041, + "start": 1582138615 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_GlUim0w59Mh6OQ", + "subscription_item": "si_GlUibAb5untQG7", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GEJFRDzYFZjJeDR8Zw42zWU/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0029", + "paid": true, + "payment_intent": null, + "period_end": 1582221341, + "period_start": 1582138615, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582225276, + "marked_uncollectible_at": null, + "paid_at": 1582225276, + "voided_at": null + }, + "subscription": "sub_GlUim0w59Mh6OQ", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582221348 + }, + { + "id": "in_1GEJFJDzYFZjJeDRzLCGoCUR", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_cycle", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582221333, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_MSJ1yZmO7y5lPHzhN7ywJLB9tc", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_MSJ1yZmO7y5lPHzhN7ywJLB9tc/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GEJFJDzYFZjJeDRWTAJVcQ0", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1582221033, + "start": 1582138691 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_GlUjNq4ZVGr4s2", + "subscription_item": "si_GlUjvZDQUcqHcs", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GEJFJDzYFZjJeDRzLCGoCUR/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0028", + "paid": true, + "payment_intent": null, + "period_end": 1582221333, + "period_start": 1582138691, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582225265, + "marked_uncollectible_at": null, + "paid_at": 1582225265, + "voided_at": null + }, + "subscription": "sub_GlUjNq4ZVGr4s2", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582221335 + }, + { + "id": "in_1GEJFDDzYFZjJeDRYjK9gMfb", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_cycle", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582221327, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_QWvJnvWWyxFYYfA6Ufni4ZjvzU", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_QWvJnvWWyxFYYfA6Ufni4ZjvzU/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GEJFDDzYFZjJeDR8UlOzXJH", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1582221027, + "start": 1582221104 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_GlqtasOM0vvM54", + "subscription_item": "si_Glqt0tLUc20y7n", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GEJFDDzYFZjJeDRYjK9gMfb/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0027", + "paid": true, + "payment_intent": null, + "period_end": 1582221327, + "period_start": 1582221104, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582225274, + "marked_uncollectible_at": null, + "paid_at": 1582225274, + "voided_at": null + }, + "subscription": "sub_GlqtasOM0vvM54", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582221329 + }, + { + "id": "in_1GEJBdDzYFZjJeDR8njLPDxC", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_create", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582221105, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_UiNg1Omh8H8UVofgDvXJcwL2lQ", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_UiNg1Omh8H8UVofgDvXJcwL2lQ/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GEJBdDzYFZjJeDRJTK2mmbZ", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1584726404, + "start": 1582221104 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_GlqtasOM0vvM54", + "subscription_item": "si_Glqt0tLUc20y7n", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GEJBdDzYFZjJeDR8njLPDxC/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0026", + "paid": true, + "payment_intent": null, + "period_end": 1582221104, + "period_start": 1582221104, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582221105, + "marked_uncollectible_at": null, + "paid_at": 1582221105, + "voided_at": null + }, + "subscription": "sub_GlqtasOM0vvM54", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582221107 + }, + { + "id": "in_1GDxkNDzYFZjJeDRRy69y28H", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_create", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582138691, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_rhuwrz3ce9GwwqP1scYZ2f8BU1", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_rhuwrz3ce9GwwqP1scYZ2f8BU1/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GDxkNDzYFZjJeDRwiGf9mgp", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1584643991, + "start": 1582138691 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_GlUjNq4ZVGr4s2", + "subscription_item": "si_GlUjvZDQUcqHcs", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GDxkNDzYFZjJeDRRy69y28H/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0025", + "paid": true, + "payment_intent": null, + "period_end": 1582138691, + "period_start": 1582138691, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582138691, + "marked_uncollectible_at": null, + "paid_at": 1582138691, + "voided_at": null + }, + "subscription": "sub_GlUjNq4ZVGr4s2", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582138695 + }, + { + "id": "in_1GDxj9DzYFZjJeDRpwkCs8My", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_create", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582138615, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_M1BkFtDZC5s4ksgBJpZiDrohLB", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_M1BkFtDZC5s4ksgBJpZiDrohLB/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GDxj9DzYFZjJeDRJLAkz8C6", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1584643915, + "start": 1582138615 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_GlUim0w59Mh6OQ", + "subscription_item": "si_GlUibAb5untQG7", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GDxj9DzYFZjJeDRpwkCs8My/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0024", + "paid": true, + "payment_intent": null, + "period_end": 1582138615, + "period_start": 1582138615, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582138615, + "marked_uncollectible_at": null, + "paid_at": 1582138615, + "voided_at": null + }, + "subscription": "sub_GlUim0w59Mh6OQ", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582138630 + }, + { + "id": "in_1GDxiEDzYFZjJeDR47VragFm", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_create", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582138558, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_1To0Ke7AXIWHR0MLADqrDq4YHD", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_1To0Ke7AXIWHR0MLADqrDq4YHD/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GDxiEDzYFZjJeDRCr0cys1S", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1584643858, + "start": 1582138558 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_GlUhSfsh4CvqKT", + "subscription_item": "si_GlUhRKEz1f6wu0", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GDxiEDzYFZjJeDR47VragFm/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0023", + "paid": true, + "payment_intent": null, + "period_end": 1582138558, + "period_start": 1582138558, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582138558, + "marked_uncollectible_at": null, + "paid_at": 1582138558, + "voided_at": null + }, + "subscription": "sub_GlUhSfsh4CvqKT", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582138560 + }, + { + "id": "in_1GDxgoDzYFZjJeDRwjguovBd", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_create", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582138470, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_JJubpCLawiMwCcRB6UD63OdcdK", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_JJubpCLawiMwCcRB6UD63OdcdK/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GDxgoDzYFZjJeDRKwFiwzLF", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1584643770, + "start": 1582138470 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_GlUgiX3ZLbSFWc", + "subscription_item": "si_GlUgyngaPGIGve", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GDxgoDzYFZjJeDRwjguovBd/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0022", + "paid": true, + "payment_intent": null, + "period_end": 1582138470, + "period_start": 1582138470, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582138470, + "marked_uncollectible_at": null, + "paid_at": 1582138471, + "voided_at": null + }, + "subscription": "sub_GlUgiX3ZLbSFWc", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582138473 + }, + { + "id": "in_1GDxgPDzYFZjJeDRmUvYzKnH", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_create", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582138445, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_WczSfrJhWTwl0RI8K2LhiCE5Tx", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_WczSfrJhWTwl0RI8K2LhiCE5Tx/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GDxgPDzYFZjJeDRJxpYUdcU", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1584643745, + "start": 1582138445 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_GlUf9jU1du2sec", + "subscription_item": "si_GlUfAEkGHyeN3H", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GDxgPDzYFZjJeDRmUvYzKnH/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0021", + "paid": true, + "payment_intent": null, + "period_end": 1582138445, + "period_start": 1582138445, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582138445, + "marked_uncollectible_at": null, + "paid_at": 1582138445, + "voided_at": null + }, + "subscription": "sub_GlUf9jU1du2sec", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582138447 + }, + { + "id": "in_1GDxfCDzYFZjJeDRocl25WjA", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_create", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582138370, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_rFubQ94u91PxyqpGNqJ6yHHpfF", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_rFubQ94u91PxyqpGNqJ6yHHpfF/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GDxfCDzYFZjJeDRror9keSq", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1584643670, + "start": 1582138370 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_GlUeGWXnVwSjJH", + "subscription_item": "si_GlUe0gtM1vB8JB", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GDxfCDzYFZjJeDRocl25WjA/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0020", + "paid": true, + "payment_intent": null, + "period_end": 1582138370, + "period_start": 1582138370, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582138370, + "marked_uncollectible_at": null, + "paid_at": 1582138370, + "voided_at": null + }, + "subscription": "sub_GlUeGWXnVwSjJH", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582138372 + }, + { + "id": "in_1GDxdFDzYFZjJeDRrt2wWyqa", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_create", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582138249, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_RrkFduIYbHxeMqJu8FKxUM8ZxH", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_RrkFduIYbHxeMqJu8FKxUM8ZxH/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GDxdFDzYFZjJeDRlPPwU89Z", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1584643549, + "start": 1582138249 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_GlUcjLnMZVWGZZ", + "subscription_item": "si_GlUctDNOUUBciF", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GDxdFDzYFZjJeDRrt2wWyqa/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0019", + "paid": true, + "payment_intent": null, + "period_end": 1582138249, + "period_start": 1582138249, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582138249, + "marked_uncollectible_at": null, + "paid_at": 1582138250, + "voided_at": null + }, + "subscription": "sub_GlUcjLnMZVWGZZ", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582138252 + }, + { + "id": "in_1GDxcYDzYFZjJeDRZmMMuZ0W", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_create", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582138206, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_d5eygzsfTcEr2IgMn74OXgRBvw", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_d5eygzsfTcEr2IgMn74OXgRBvw/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GDxcYDzYFZjJeDRWaUKVS3I", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1584643506, + "start": 1582138206 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_GlUbwEpPfiWMiR", + "subscription_item": "si_GlUbSIWttwvCCt", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GDxcYDzYFZjJeDRZmMMuZ0W/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0018", + "paid": true, + "payment_intent": null, + "period_end": 1582138206, + "period_start": 1582138206, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582138206, + "marked_uncollectible_at": null, + "paid_at": 1582138207, + "voided_at": null + }, + "subscription": "sub_GlUbwEpPfiWMiR", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582138208 + }, + { + "id": "in_1GDxb1DzYFZjJeDRBWN5wajW", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_create", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582138111, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_hq4lxUb7GswDtfaOuFwijDW2YN", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_hq4lxUb7GswDtfaOuFwijDW2YN/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GDxb1DzYFZjJeDRiCp9lx5M", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1584643411, + "start": 1582138111 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_GlUaM6dfbKUGds", + "subscription_item": "si_GlUa1FAupTxScr", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GDxb1DzYFZjJeDRBWN5wajW/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0017", + "paid": true, + "payment_intent": null, + "period_end": 1582138111, + "period_start": 1582138111, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582138111, + "marked_uncollectible_at": null, + "paid_at": 1582138111, + "voided_at": null + }, + "subscription": "sub_GlUaM6dfbKUGds", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582138117 + }, + { + "id": "in_1GDxZGDzYFZjJeDR6mO8JXll", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_create", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582138002, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_fWHcMkzSj2EfKwKuqsJ4tSvYX3", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_fWHcMkzSj2EfKwKuqsJ4tSvYX3/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GDxZGDzYFZjJeDRAHGXAhN3", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1584643302, + "start": 1582138002 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_GlUYlvLEIodNSY", + "subscription_item": "si_GlUYJxIix069vN", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GDxZGDzYFZjJeDR6mO8JXll/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0016", + "paid": true, + "payment_intent": null, + "period_end": 1582138002, + "period_start": 1582138002, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582138002, + "marked_uncollectible_at": null, + "paid_at": 1582138003, + "voided_at": null + }, + "subscription": "sub_GlUYlvLEIodNSY", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582138012 + }, + { + "id": "in_1GDxYFDzYFZjJeDRoW5GM5A4", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_create", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582137939, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_vFhcPAenWEmvGwQ5NJHWXvh1vS", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_vFhcPAenWEmvGwQ5NJHWXvh1vS/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GDxYFDzYFZjJeDRK0bSzvdY", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1584643239, + "start": 1582137939 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_GlUXi8Yd9Xfe0M", + "subscription_item": "si_GlUXLBqPbEiFcP", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GDxYFDzYFZjJeDRoW5GM5A4/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0015", + "paid": true, + "payment_intent": null, + "period_end": 1582137939, + "period_start": 1582137939, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582137939, + "marked_uncollectible_at": null, + "paid_at": 1582137939, + "voided_at": null + }, + "subscription": "sub_GlUXi8Yd9Xfe0M", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582137943 + }, + { + "id": "in_1GDxKoDzYFZjJeDR1hb7KXCm", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_create", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582137106, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_o5gRjsr8hXWwHccS7JI5Hgaf2S", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_o5gRjsr8hXWwHccS7JI5Hgaf2S/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GDxKoDzYFZjJeDRDz6L3Z5q", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1584642406, + "start": 1582137106 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_GlUJoR7VLN8sm3", + "subscription_item": "si_GlUJXbUYz9yJoN", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GDxKoDzYFZjJeDR1hb7KXCm/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0014", + "paid": true, + "payment_intent": null, + "period_end": 1582137106, + "period_start": 1582137106, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582137106, + "marked_uncollectible_at": null, + "paid_at": 1582137106, + "voided_at": null + }, + "subscription": "sub_GlUJoR7VLN8sm3", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582137108 + }, + { + "id": "in_1GDxHLDzYFZjJeDR5M59idXg", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_create", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582136891, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_GTfX0b7iGz0KX0kv1ZtP5cAcsH", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_GTfX0b7iGz0KX0kv1ZtP5cAcsH/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GDxHLDzYFZjJeDRAaOsz7aC", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1584642191, + "start": 1582136891 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_GlUFa2ffWb2BP0", + "subscription_item": "si_GlUFc6c4jMzCKf", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GDxHLDzYFZjJeDR5M59idXg/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0013", + "paid": true, + "payment_intent": null, + "period_end": 1582136891, + "period_start": 1582136891, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582136891, + "marked_uncollectible_at": null, + "paid_at": 1582136891, + "voided_at": null + }, + "subscription": "sub_GlUFa2ffWb2BP0", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582136893 + }, + { + "id": "in_1GDxGvDzYFZjJeDR59M9BVhX", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_create", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582136865, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_t5r6LdZDabqG3GZTzBNUcD9QGk", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_t5r6LdZDabqG3GZTzBNUcD9QGk/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GDxGvDzYFZjJeDRJoJvSRpy", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1584642165, + "start": 1582136865 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_GlUFxZvyuLjlPS", + "subscription_item": "si_GlUFUFOAAWpwpX", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GDxGvDzYFZjJeDR59M9BVhX/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0012", + "paid": true, + "payment_intent": null, + "period_end": 1582136865, + "period_start": 1582136865, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582136865, + "marked_uncollectible_at": null, + "paid_at": 1582136865, + "voided_at": null + }, + "subscription": "sub_GlUFxZvyuLjlPS", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582136867 + }, + { + "id": "in_1GDxGODzYFZjJeDRUiUY9ELj", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_create", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582136832, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_uKoaMQiGfhpvIVEH70Adso6r4c", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_uKoaMQiGfhpvIVEH70Adso6r4c/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GDxGODzYFZjJeDReTY5QG0R", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1584642132, + "start": 1582136832 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_GlUEGxNOQrruh9", + "subscription_item": "si_GlUExJvwt5ZcYp", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GDxGODzYFZjJeDRUiUY9ELj/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0011", + "paid": true, + "payment_intent": null, + "period_end": 1582136832, + "period_start": 1582136832, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582136832, + "marked_uncollectible_at": null, + "paid_at": 1582136832, + "voided_at": null + }, + "subscription": "sub_GlUEGxNOQrruh9", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582136834 + }, + { + "id": "in_1GDwO3DzYFZjJeDR5OCtH3Lj", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_create", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582133463, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_S6F5i4exSwJgu0OOXHcWMdpIlB", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_S6F5i4exSwJgu0OOXHcWMdpIlB/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GDwO3DzYFZjJeDRfyutrdOz", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1584638762, + "start": 1582133462 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_GlTKjrybUjVIQu", + "subscription_item": "si_GlTKPDpfqQmojf", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GDwO3DzYFZjJeDR5OCtH3Lj/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0010", + "paid": true, + "payment_intent": null, + "period_end": 1582133462, + "period_start": 1582133462, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582133463, + "marked_uncollectible_at": null, + "paid_at": 1582133463, + "voided_at": null + }, + "subscription": "sub_GlTKjrybUjVIQu", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582133466 + }, + { + "id": "in_1GDwLQDzYFZjJeDRorS3bmKJ", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_create", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582133300, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_YT40ktSmqfcaKtMv7uNC3HbYoE", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_YT40ktSmqfcaKtMv7uNC3HbYoE/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GDwLQDzYFZjJeDRQPQSSjH9", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1584638600, + "start": 1582133300 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_GlTIF4mdqfCD3K", + "subscription_item": "si_GlTIbSZLazgI3h", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GDwLQDzYFZjJeDRorS3bmKJ/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0009", + "paid": true, + "payment_intent": null, + "period_end": 1582133300, + "period_start": 1582133300, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582133300, + "marked_uncollectible_at": null, + "paid_at": 1582133300, + "voided_at": null + }, + "subscription": "sub_GlTIF4mdqfCD3K", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582133308 + }, + { + "id": "in_1GDfKADzYFZjJeDRtZs12b7l", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_create", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582067874, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_zEoIn1pNoov3E6Ou6r4oVbt9kV", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_zEoIn1pNoov3E6Ou6r4oVbt9kV/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GDfKADzYFZjJeDRGHYqRjxU", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1584573174, + "start": 1582067874 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_GlBhPV1GGj7yK3", + "subscription_item": "si_GlBh2g4znedahj", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GDfKADzYFZjJeDRtZs12b7l/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0008", + "paid": true, + "payment_intent": null, + "period_end": 1582067874, + "period_start": 1582067874, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582067874, + "marked_uncollectible_at": null, + "paid_at": 1582067875, + "voided_at": null + }, + "subscription": "sub_GlBhPV1GGj7yK3", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582067876 + }, + { + "id": "in_1GDeqBDzYFZjJeDRVbCjnTRU", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_create", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582066015, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_1osQL1WGWfuiE37cZ7OkGxYYkH", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_1osQL1WGWfuiE37cZ7OkGxYYkH/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GDeqBDzYFZjJeDRRqeS96KM", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1584571315, + "start": 1582066015 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_GlBCSGqVrCpCzo", + "subscription_item": "si_GlBCwgK9koazse", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GDeqBDzYFZjJeDRVbCjnTRU/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0007", + "paid": true, + "payment_intent": null, + "period_end": 1582066015, + "period_start": 1582066015, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582066015, + "marked_uncollectible_at": null, + "paid_at": 1582066016, + "voided_at": null + }, + "subscription": "sub_GlBCSGqVrCpCzo", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582066018 + }, + { + "id": "in_1GDepMDzYFZjJeDRaOCO9OMU", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_create", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582065964, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_qXrWhuLLfrF36HiRTP8apK3JbK", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_qXrWhuLLfrF36HiRTP8apK3JbK/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GDepMDzYFZjJeDRPVBFZ8xs", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1584571264, + "start": 1582065964 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_GlBBuRPHRwBLOw", + "subscription_item": "si_GlBBtScHFXv3Yr", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GDepMDzYFZjJeDRaOCO9OMU/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0006", + "paid": true, + "payment_intent": null, + "period_end": 1582065964, + "period_start": 1582065964, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582065964, + "marked_uncollectible_at": null, + "paid_at": 1582065964, + "voided_at": null + }, + "subscription": "sub_GlBBuRPHRwBLOw", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582065976 + }, + { + "id": "in_1GDemjDzYFZjJeDR7tMDvX3K", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_create", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582065801, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_IcGqiQzu5KtI2dRtZQpMDn5OTL", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_IcGqiQzu5KtI2dRtZQpMDn5OTL/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GDemjDzYFZjJeDR4FnogVQr", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1584571101, + "start": 1582065801 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_GlB9cNSo0coTLI", + "subscription_item": "si_GlB9l9qhL3AE2Q", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GDemjDzYFZjJeDR7tMDvX3K/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0005", + "paid": true, + "payment_intent": null, + "period_end": 1582065801, + "period_start": 1582065801, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582065801, + "marked_uncollectible_at": null, + "paid_at": 1582065801, + "voided_at": null + }, + "subscription": "sub_GlB9cNSo0coTLI", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582065803 + }, + { + "id": "in_1GDemDDzYFZjJeDRGtcrZ1Pz", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_create", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582065769, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_Ld0LCENC7okwHhOMWhKQa6FRhK", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_Ld0LCENC7okwHhOMWhKQa6FRhK/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GDemDDzYFZjJeDR4jWPn4VJ", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1584571069, + "start": 1582065769 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_GlB8tLfO9NfXFY", + "subscription_item": "si_GlB8SOD5ZD4KDF", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GDemDDzYFZjJeDRGtcrZ1Pz/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0004", + "paid": true, + "payment_intent": null, + "period_end": 1582065769, + "period_start": 1582065769, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582065769, + "marked_uncollectible_at": null, + "paid_at": 1582065769, + "voided_at": null + }, + "subscription": "sub_GlB8tLfO9NfXFY", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582065771 + }, + { + "id": "in_1GDelxDzYFZjJeDRAZms22bW", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_create", + "charge": null, + "collection_method": "charge_automatically", + "created": 1582065753, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_5ZxLXNu4gzVzENdYHtFnJsTGPM", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_5ZxLXNu4gzVzENdYHtFnJsTGPM/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GDelxDzYFZjJeDRGe7duNko", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB Cloud (at $11.672 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1584571053, + "start": 1582065753 + }, + "plan": { + "id": "plan_GahFKupGaOFIuk", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "1167.2", + "billing_scheme": "per_unit", + "created": 1579648193, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "256GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_GlB8xHZn8RSgRA", + "subscription_item": "si_GlB8E5eZBRuAtP", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GDelxDzYFZjJeDRAZms22bW/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0003", + "paid": true, + "payment_intent": null, + "period_end": 1582065753, + "period_start": 1582065753, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": 0, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1582065753, + "marked_uncollectible_at": null, + "paid_at": 1582065753, + "voided_at": null + }, + "subscription": "sub_GlB8xHZn8RSgRA", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1582065755 + }, + { + "id": "in_1GDEmyDzYFZjJeDRSrd8yBJ8", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 43887, + "amount_paid": 43887, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 1, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_cycle", + "charge": "ch_1GDFzvDzYFZjJeDRnJaVK3JI", + "collection_method": "charge_automatically", + "created": 1581965872, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": 0, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_B3J7CEcDPJY1GGi9rPVxaAZtpy", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_B3J7CEcDPJY1GGi9rPVxaAZtpy/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1GDEmyDzYFZjJeDRlPA4VkCr", + "object": "line_item", + "amount": 47087, + "currency": "usd", + "description": "1090 hours × HarperDB Cloud (at $0.4319940823 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1581965572, + "start": 1579287455 + }, + "plan": { + "id": "plan_GZ6IV9XuGlb8a6", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "43.19940823", + "billing_scheme": "per_unit", + "created": 1579280118, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "8GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 1090, + "subscription": "sub_GZ8HPy9YV3jLA0", + "subscription_item": "si_GZ8HaCj22AxFzy", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1GDEmyDzYFZjJeDRSrd8yBJ8/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0002", + "paid": true, + "payment_intent": "pi_1GDFzuDzYFZjJeDR8HakZma4", + "period_end": 1581965855, + "period_start": 1579287455, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": null, + "starting_balance": -3200, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1581970518, + "marked_uncollectible_at": null, + "paid_at": 1581970520, + "voided_at": null + }, + "subscription": "sub_GZ8HPy9YV3jLA0", + "subtotal": 47087, + "tax": null, + "tax_percent": null, + "total": 47087, + "total_tax_amounts": [], + "webhooks_delivered_at": 1581965876 + }, + { + "id": "in_1G200hDzYFZjJeDR6ZHH7yjz", + "object": "invoice", + "account_country": "US", + "account_name": "HarperDB, Inc.", + "amount_due": 0, + "amount_paid": 0, + "amount_remaining": 0, + "application_fee_amount": null, + "attempt_count": 0, + "attempted": true, + "auto_advance": false, + "billing_reason": "subscription_create", + "charge": null, + "collection_method": "charge_automatically", + "created": 1579287455, + "currency": "usd", + "custom_fields": null, + "customer": "cus_GY4SPXl27SyCPa", + "customer_address": null, + "customer_email": "gkhan@harperdb.io", + "customer_name": "Genghis Khan", + "customer_phone": null, + "customer_shipping": { + "address": { + "city": "", + "country": "", + "line1": "", + "line2": "", + "postal_code": "", + "state": "" + }, + "name": "Genghis Khan", + "phone": "" + }, + "customer_tax_exempt": "none", + "customer_tax_ids": [], + "default_payment_method": null, + "default_source": null, + "default_tax_rates": [], + "description": null, + "discount": null, + "due_date": null, + "ending_balance": -3200, + "footer": null, + "hosted_invoice_url": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_qBh3BJtfiJC1uebvwSUgmtjgTM", + "invoice_pdf": "https://pay.stripe.com/invoice/acct_1G0wyBDzYFZjJeDR/invst_qBh3BJtfiJC1uebvwSUgmtjgTM/pdf", + "lines": { + "object": "list", + "data": [ + { + "id": "il_1G200hDzYFZjJeDRdpbT8u63", + "object": "line_item", + "amount": 0, + "currency": "usd", + "description": "0 hours × HarperDB DBaaS (at $0.4319940823 / month)", + "discountable": true, + "livemode": false, + "metadata": {}, + "period": { + "end": 1581965555, + "start": 1579287455 + }, + "plan": { + "id": "plan_GZ6IV9XuGlb8a6", + "object": "plan", + "active": true, + "aggregate_usage": "sum", + "amount": null, + "amount_decimal": "43.19940823", + "billing_scheme": "per_unit", + "created": 1579280118, + "currency": "usd", + "interval": "month", + "interval_count": 1, + "livemode": false, + "metadata": {}, + "nickname": "8GB", + "product": "prod_GZ5dZoytHIPTfD", + "tiers": null, + "tiers_mode": null, + "transform_usage": null, + "trial_period_days": null, + "usage_type": "metered" + }, + "proration": false, + "quantity": 0, + "subscription": "sub_GZ8HPy9YV3jLA0", + "subscription_item": "si_GZ8HaCj22AxFzy", + "tax_amounts": [], + "tax_rates": [], + "type": "subscription" + } + ], + "has_more": false, + "total_count": 1, + "url": "/v1/invoices/in_1G200hDzYFZjJeDR6ZHH7yjz/lines" + }, + "livemode": false, + "metadata": {}, + "next_payment_attempt": null, + "number": "8FA34C0A-0001", + "paid": true, + "payment_intent": null, + "period_end": 1579287455, + "period_start": 1579287455, + "post_payment_credit_notes_amount": 0, + "pre_payment_credit_notes_amount": 0, + "receipt_number": "2963-3654", + "starting_balance": -3200, + "statement_descriptor": null, + "status": "paid", + "status_transitions": { + "finalized_at": 1579287455, + "marked_uncollectible_at": null, + "paid_at": 1579287456, + "voided_at": null + }, + "subscription": "sub_GZ8HPy9YV3jLA0", + "subtotal": 0, + "tax": null, + "tax_percent": null, + "total": 0, + "total_tax_amounts": [], + "webhooks_delivered_at": 1579287456 + } + ] +} diff --git a/mock_data/LMS_API.licenses.json b/mock_data/LMS_API.licenses.json index 65bccff9b..492292c20 100644 --- a/mock_data/LMS_API.licenses.json +++ b/mock_data/LMS_API.licenses.json @@ -1,98 +1,101 @@ -[ - { - "license_id": "license-guid-0", - "instance_id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", - "customer_id": "customer-id-guid", - "version": "2.0.14-BETA", - "exp_date": 1582669908353, - "storage_type": "LMDB", - "ram_allocation": 1, - "fingerprint": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", - "stripe_product_id": "prod_GoB3yLzygPeGMu", - "stripe_plan_id": "plan_GoB4aP5U6PuR6u" - }, - { - "license_id": "license-guid-1", - "instance_id": "4f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20de", - "customer_id": "customer-id-guid", - "version": "2.0.14-BETA", - "exp_date": 1582669908353, - "storage_type": "LMDB", - "ram_allocation": 1, - "fingerprint": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", - "stripe_product_id": "prod_Gh1XXQx6J8YaJl", - "stripe_plan_id": "plan_GoB54KxG5JRkwm" - }, - { - "license_id": "license-guid-2", - "instance_id": "cee98941-8975-44d6-88e6-9a1a4fa02b25", - "customer_id": "customer-id-guid", - "version": "2.0.14-BETA", - "exp_date": 1582669908353, - "storage_type": "LMDB", - "ram_allocation": 1, - "fingerprint": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", - "stripe_product_id": "prod_Gh1XXQx6J8YaJl", - "stripe_plan_id": "2GB_Beta" - }, - { - "license_id": "license-guid-3", - "instance_id": "a32b2464-b5ca-4b9d-a10f-c6da23a7dcbe", - "customer_id": "customer-id-guid", - "version": "2.0.14-BETA", - "exp_date": 1582669908353, - "storage_type": "LMDB", - "ram_allocation": 1, - "fingerprint": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", - "stripe_product_id": "prod_Gh1XXQx6J8YaJl", - "stripe_plan_id": "4GB_Beta" - }, - { - "license_id": "license-guid-4", - "instance_id": "7a60b51d-7983-4659-a34b-cfb578a4db54", - "customer_id": "customer-id-guid", - "version": "2.0.14-BETA", - "exp_date": 1582669908353, - "storage_type": "LMDB", - "ram_allocation": 1, - "fingerprint": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", - "stripe_product_id": "prod_Gh1XXQx6J8YaJl", - "stripe_plan_id": "8GB_Beta" - }, - { - "license_id": "license-guid-5", - "instance_id": "f0d29404-9fb1-4d66-ab98-41f05e1ac665", - "customer_id": "customer-id-guid", - "version": "2.0.14-BETA", - "exp_date": 1582669908353, - "storage_type": "LMDB", - "ram_allocation": 1, - "fingerprint": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", - "stripe_product_id": "prod_Gh1XXQx6J8YaJl", - "stripe_plan_id": "4GB_Beta" - }, - { - "license_id": "license-guid-6", - "instance_id": "ccf2b958-0b0e-4bf6-a81d-6abee1e4eec5", - "customer_id": "customer-id-guid", - "version": "2.0.14-BETA", - "exp_date": 1582669908353, - "storage_type": "LMDB", - "ram_allocation": 1, - "fingerprint": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", - "stripe_product_id": "prod_Gh1XXQx6J8YaJl", - "stripe_plan_id": "2GB_Beta" - }, - { - "license_id": "license-guid-7", - "instance_id": "8291c224-6911-4155-b5a9-34559e5a45ff", - "customer_id": "customer-id-guid", - "version": "2.0.14-BETA", - "exp_date": 1582669908353, - "storage_type": "LMDB", - "ram_allocation": 1, - "fingerprint": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", - "stripe_product_id": "prod_Gh1XXQx6J8YaJl", - "stripe_plan_id": "plan_GoB54KxG5JRkwm" - } -] +{ + "statusCode": 200, + "body": [ + { + "license_id": "license-guid-0", + "instance_id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", + "customer_id": "customer-id-guid", + "version": "2.0.14-BETA", + "exp_date": 1582669908353, + "storage_type": "LMDB", + "ram_allocation": 1, + "fingerprint": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", + "stripe_product_id": "prod_GoB3yLzygPeGMu", + "stripe_plan_id": "plan_GoB4aP5U6PuR6u" + }, + { + "license_id": "license-guid-1", + "instance_id": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20de", + "customer_id": "customer-id-guid", + "version": "2.0.14-BETA", + "exp_date": 1582669908353, + "storage_type": "LMDB", + "ram_allocation": 1, + "fingerprint": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", + "stripe_product_id": "prod_Gh1XXQx6J8YaJl", + "stripe_plan_id": "plan_GoB54KxG5JRkwm" + }, + { + "license_id": "license-guid-2", + "instance_id": "cee98941-8975-44d6-88e6-9a1a4fa02b25", + "customer_id": "customer-id-guid", + "version": "2.0.14-BETA", + "exp_date": 1582669908353, + "storage_type": "LMDB", + "ram_allocation": 1, + "fingerprint": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", + "stripe_product_id": "prod_Gh1XXQx6J8YaJl", + "stripe_plan_id": "2GB_Beta" + }, + { + "license_id": "license-guid-3", + "instance_id": "a32b2464-b5ca-4b9d-a10f-c6da23a7dcbe", + "customer_id": "customer-id-guid", + "version": "2.0.14-BETA", + "exp_date": 1582669908353, + "storage_type": "LMDB", + "ram_allocation": 1, + "fingerprint": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", + "stripe_product_id": "prod_Gh1XXQx6J8YaJl", + "stripe_plan_id": "4GB_Beta" + }, + { + "license_id": "license-guid-4", + "instance_id": "7a60b51d-7983-4659-a34b-cfb578a4db54", + "customer_id": "customer-id-guid", + "version": "2.0.14-BETA", + "exp_date": 1582669908353, + "storage_type": "LMDB", + "ram_allocation": 1, + "fingerprint": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", + "stripe_product_id": "prod_Gh1XXQx6J8YaJl", + "stripe_plan_id": "8GB_Beta" + }, + { + "license_id": "license-guid-5", + "instance_id": "f0d29404-9fb1-4d66-ab98-41f05e1ac665", + "customer_id": "customer-id-guid", + "version": "2.0.14-BETA", + "exp_date": 1582669908353, + "storage_type": "LMDB", + "ram_allocation": 1, + "fingerprint": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", + "stripe_product_id": "prod_Gh1XXQx6J8YaJl", + "stripe_plan_id": "4GB_Beta" + }, + { + "license_id": "license-guid-6", + "instance_id": "ccf2b958-0b0e-4bf6-a81d-6abee1e4eec5", + "customer_id": "customer-id-guid", + "version": "2.0.14-BETA", + "exp_date": 1582669908353, + "storage_type": "LMDB", + "ram_allocation": 1, + "fingerprint": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", + "stripe_product_id": "prod_Gh1XXQx6J8YaJl", + "stripe_plan_id": "2GB_Beta" + }, + { + "license_id": "license-guid-7", + "instance_id": "8291c224-6911-4155-b5a9-34559e5a45ff", + "customer_id": "customer-id-guid", + "version": "2.0.14-BETA", + "exp_date": 1582669908353, + "storage_type": "LMDB", + "ram_allocation": 1, + "fingerprint": "3f6c9e35-f6a8-4fb7-b09f-8b3e7d4e20df", + "stripe_product_id": "prod_Gh1XXQx6J8YaJl", + "stripe_plan_id": "plan_GoB54KxG5JRkwm" + } + ] +} diff --git a/mock_data/LMS_API.user.json b/mock_data/LMS_API.user.json index b74e9569d..6bbfec9dd 100644 --- a/mock_data/LMS_API.user.json +++ b/mock_data/LMS_API.user.json @@ -1,7 +1,10 @@ { - "id": "user-id-guid1", - "firstname": "First1", - "lastname": "Last1", - "email": "user1@harperdb.io", - "customer_id": "customer-id-guid" + "statusCode": 200, + "body": { + "id": "user-id-guid1", + "firstname": "First1", + "lastname": "Last1", + "email": "user1@harperdb.io", + "customer_id": "customer-id-guid" + } } diff --git a/package-lock.json b/package-lock.json index e28b42eac..2ffb9daab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12826,9 +12826,9 @@ "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" }, "query-string": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.11.0.tgz", - "integrity": "sha512-jS+me8X3OEGFTsF6kF+vUUMFG/d3WUCvD7bHhfZP5784nOq1pjj8yau/u86nfOncmcN6ZkSWKWkKAvv/MGxzLA==", + "version": "6.11.1", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.11.1.tgz", + "integrity": "sha512-1ZvJOUl8ifkkBxu2ByVM/8GijMIPx+cef7u3yroO3Ogm4DOdZcF5dcrWTIlSHe3Pg/mtlt6/eFjObDfJureZZA==", "requires": { "decode-uri-component": "^0.2.0", "split-on-first": "^1.0.0", @@ -15910,9 +15910,9 @@ } }, "webpack": { - "version": "4.41.6", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.41.6.tgz", - "integrity": "sha512-yxXfV0Zv9WMGRD+QexkZzmGIh54bsvEs+9aRWxnN8erLWEOehAKUTeNBoUbA6HPEZPlRo7KDi2ZcNveoZgK9MA==", + "version": "4.42.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.42.0.tgz", + "integrity": "sha512-EzJRHvwQyBiYrYqhyjW9AqM90dE4+s1/XtCfn7uWg6cS72zH+2VPFAlsnW0+W0cDi0XRjNKUMoJtpSi50+Ph6w==", "dev": true, "requires": { "@webassemblyjs/ast": "1.8.5", diff --git a/package.json b/package.json index 9b9de59a5..b920f8ba5 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "@stripe/stripe-js": "^1.0.2", "font-awesome": "^4.7.0", "json-stringify-pretty-compact": "^2.0.0", - "query-string": "^6.11.0", + "query-string": "^6.11.1", "react": "^16.13.0", "react-alert": "^6.0.0", "react-card-flip": "^1.0.10", @@ -70,7 +70,7 @@ "sass-lint": "^1.13.1", "sass-loader": "^8.0.2", "terser-webpack-plugin": "^2.3.5", - "webpack": "^4.41.6", + "webpack": "^4.42.0", "webpack-cli": "^3.3.11", "webpack-dev-server": "^3.10.3", "webpack-merge": "^4.2.2" diff --git a/src/api/instance/addUser.js b/src/api/instance/addUser.js new file mode 100644 index 000000000..7cffef00f --- /dev/null +++ b/src/api/instance/addUser.js @@ -0,0 +1,4 @@ +import queryInstance from '../queryInstance'; + +export default async ({ auth, role, username, password }) => queryInstance({ operation: 'add_user', role, username, password, active: true }, auth); + diff --git a/src/api/instance/dropUser.js b/src/api/instance/dropUser.js new file mode 100644 index 000000000..5664f2554 --- /dev/null +++ b/src/api/instance/dropUser.js @@ -0,0 +1,3 @@ +import queryInstance from '../queryInstance'; + +export default async ({ auth, username }) => queryInstance({ operation: 'drop_user', username }, auth); diff --git a/src/api/instance/listUsers.js b/src/api/instance/listUsers.js new file mode 100644 index 000000000..813fe3aa9 --- /dev/null +++ b/src/api/instance/listUsers.js @@ -0,0 +1,4 @@ +import queryInstance from '../queryInstance'; + +export default async ({ auth }) => queryInstance({ operation: 'list_users' }, auth); + diff --git a/src/api/lms/addCustomer.js b/src/api/lms/addCustomer.js new file mode 100644 index 000000000..23db06ed8 --- /dev/null +++ b/src/api/lms/addCustomer.js @@ -0,0 +1,12 @@ +import queryLMS from '../queryLMS'; + +export default async ({ auth, payload: { firstname, lastname, email, company, subdomain } }) => { + const response = await queryLMS({ + endpoint: 'addCustomer', + method: 'POST', + payload: { firstname, lastname, email, company, subdomain }, + auth, + }); + + return response.body; +}; diff --git a/src/api/lms/addInstance.js b/src/api/lms/addInstance.js index 2cd12f334..e38093097 100644 --- a/src/api/lms/addInstance.js +++ b/src/api/lms/addInstance.js @@ -1,8 +1,12 @@ import queryLMS from '../queryLMS'; -export default async ({ auth, payload }) => queryLMS({ - endpoint: 'addInstance', - method: 'POST', - payload, - auth, -}); +export default async ({ auth, payload }) => { + const response = await queryLMS({ + endpoint: 'addInstance', + method: 'POST', + payload, + auth, + }); + + return response.body; +}; diff --git a/src/api/lms/addLicense.js b/src/api/lms/addLicense.js index fa4e6b99c..9a9bc0a35 100644 --- a/src/api/lms/addLicense.js +++ b/src/api/lms/addLicense.js @@ -1,8 +1,12 @@ import queryLMS from '../queryLMS'; -export default async ({ auth, payload: { instance_id, customer_id, stripe_product_id, fingerprint } }) => queryLMS({ - endpoint: 'addLicense', - method: 'POST', - payload: { instance_id, customer_id, stripe_product_id, fingerprint }, - auth, -}); +export default async ({ auth, payload: { instance_id, customer_id, stripe_product_id, fingerprint } }) => { + const response = await queryLMS({ + endpoint: 'addLicense', + method: 'POST', + payload: { instance_id, customer_id, stripe_product_id, fingerprint }, + auth, + }); + + return response.body +}; diff --git a/src/api/lms/addPaymentMethod.js b/src/api/lms/addPaymentMethod.js index 7d4761825..5e078f6f9 100644 --- a/src/api/lms/addPaymentMethod.js +++ b/src/api/lms/addPaymentMethod.js @@ -1,8 +1,12 @@ import queryLMS from '../queryLMS'; -export default async ({ auth, payload }) => queryLMS({ - endpoint: 'addPaymentMethod', - method: 'POST', - payload, - auth, -}); +export default async ({ auth, payload: { payment_method_id, stripe_customer_id } }) => { + const response = await queryLMS({ + endpoint: 'addPaymentMethod', + method: 'POST', + payload: { payment_method_id, stripe_customer_id }, + auth, + }); + + return response.body; +}; diff --git a/src/api/lms/addTCAcceptance.js b/src/api/lms/addTCAcceptance.js index c8e7f0bdc..c68a656ce 100644 --- a/src/api/lms/addTCAcceptance.js +++ b/src/api/lms/addTCAcceptance.js @@ -1,8 +1,12 @@ import queryLMS from '../queryLMS'; -export default async ({ auth, payload }) => queryLMS({ - endpoint: 'addTCAcceptance', - method: 'POST', - payload, - auth, -}); +export default async ({ auth, payload }) => { + const response = await queryLMS({ + endpoint: 'addTCAcceptance', + method: 'POST', + payload, + auth, + }); + + return response.body; +}; diff --git a/src/api/lms/addUser.js b/src/api/lms/addUser.js index dd67d5cfe..aeafabc77 100644 --- a/src/api/lms/addUser.js +++ b/src/api/lms/addUser.js @@ -1,8 +1,12 @@ import queryLMS from '../queryLMS'; -export default async ({ auth, payload }) => queryLMS({ - endpoint: 'addUser', - method: 'POST', - payload, - auth, -}); +export default async ({ auth, payload }) => { + const response = await queryLMS({ + endpoint: 'addUser', + method: 'POST', + payload, + auth, + }); + + return response.body; +}; diff --git a/src/api/lms/getCustomer.js b/src/api/lms/getCustomer.js index 5f0436c30..7ea8e5467 100644 --- a/src/api/lms/getCustomer.js +++ b/src/api/lms/getCustomer.js @@ -1,7 +1,11 @@ import queryLMS from '../queryLMS'; -export default async ({ auth }) => queryLMS({ - endpoint: 'getCustomer', - method: 'POST', - auth, -}); +export default async ({ auth }) => { + const response = await queryLMS({ + endpoint: 'getCustomer', + method: 'POST', + auth, + }); + + return response.body; +}; diff --git a/src/api/lms/getInstances.js b/src/api/lms/getInstances.js index 8cfec0985..411e67d7a 100644 --- a/src/api/lms/getInstances.js +++ b/src/api/lms/getInstances.js @@ -1,7 +1,17 @@ import queryLMS from '../queryLMS'; -export default async ({ auth }) => queryLMS({ - endpoint: 'getInstances', - method: 'POST', - auth, -}); +export default async ({ auth, products, regions, licenses }) => { + const response = await queryLMS({ + endpoint: 'getInstances', + method: 'POST', + auth, + }); + + return response.body.map((i) => ({ + ...i, + compute: products[i.is_local ? 'localCompute' : 'cloudCompute'].find((p) => p.value === i.stripe_plan_id), + storage: i.is_local ? false : products.cloudStorage.find((p) => p.value === i.storage_qty_gb), + region: i.is_local ? false : regions.find((r) => r.value === i.instance_region), + license: licenses.find((l) => l.instance_id === i.id), + })); +}; diff --git a/src/api/lms/getInvoices.js b/src/api/lms/getInvoices.js index ab5c1402e..1d1998fcc 100644 --- a/src/api/lms/getInvoices.js +++ b/src/api/lms/getInvoices.js @@ -1,7 +1,11 @@ import queryLMS from '../queryLMS'; -export default async ({ auth }) => queryLMS({ - endpoint: 'getInvoices', - method: 'POST', - auth, -}); +export default async ({ auth }) => { + const response = await queryLMS({ + endpoint: 'getInvoices', + method: 'POST', + auth, + }); + + return response.body; +}; diff --git a/src/api/lms/getLicenses.js b/src/api/lms/getLicenses.js index 47d26e12a..298f76660 100644 --- a/src/api/lms/getLicenses.js +++ b/src/api/lms/getLicenses.js @@ -1,7 +1,11 @@ import queryLMS from '../queryLMS'; -export default async ({ auth }) => queryLMS({ - endpoint: 'getLicenses', - method: 'POST', - auth, -}); +export default async ({ auth }) => { + const response = await queryLMS({ + endpoint: 'getLicenses', + method: 'POST', + auth, + }); + + return response.body; +}; diff --git a/src/api/lms/getProducts.js b/src/api/lms/getProducts.js index 224c027c1..c1fe916e0 100644 --- a/src/api/lms/getProducts.js +++ b/src/api/lms/getProducts.js @@ -22,16 +22,16 @@ const buildRadioSelectStorageOptions = (size, { tiers, interval }) => { }; }; -export default async ({ auth }) => { - const newProducts = await queryLMS({ +export default async () => { + const response = await queryLMS({ endpoint: 'getProducts', method: 'POST', - auth, + auth: { user: 'harperdb', pass: 'harperdb' }, }); - const localComputeOptions = newProducts.body.find((p) => p.name === 'HarperDB Local Annual'); - const cloudComputeOptions = newProducts.body.find((p) => p.name === 'HarperDB Cloud Beta'); - const cloudStoragePlans = newProducts.body.find((p) => p.name === 'HarperDB Cloud Storage'); + const localComputeOptions = response.body.find((p) => p.name === 'HarperDB Local Annual'); + const cloudComputeOptions = response.body.find((p) => p.name === 'HarperDB Cloud Beta'); + const cloudStoragePlans = response.body.find((p) => p.name === 'HarperDB Cloud Storage'); const cloudStorageOptions = [10, 100, 250, 500, 1000]; const cloudStorage = cloudStorageOptions.map((size) => buildRadioSelectStorageOptions(size, cloudStoragePlans.plans[0])); diff --git a/src/api/lms/getRegions.js b/src/api/lms/getRegions.js index f83e68e25..54cafab4a 100644 --- a/src/api/lms/getRegions.js +++ b/src/api/lms/getRegions.js @@ -1,7 +1,10 @@ import queryLMS from '../queryLMS'; -export default async ({ auth }) => queryLMS({ - endpoint: 'getRegions', - method: 'POST', - auth, -}); +export default async () => { + const response = await queryLMS({ + endpoint: 'getRegions', + method: 'POST', + auth: { user: 'harperdb', pass: 'harperdb' }, + }); + return response.body; +}; diff --git a/src/api/lms/getUser.js b/src/api/lms/getUser.js index f10609203..9fdbc69d0 100644 --- a/src/api/lms/getUser.js +++ b/src/api/lms/getUser.js @@ -1,7 +1,12 @@ import queryLMS from '../queryLMS'; -export default async ({ auth }) => queryLMS({ - endpoint: 'getUser', - method: 'POST', - auth, -}); +export default async ({ auth, payload: { email } }) => { + const response = await queryLMS({ + endpoint: 'getUser', + method: 'POST', + payload: { email }, + auth, + }); + + return response.body; +}; diff --git a/src/api/lms/getUsers.js b/src/api/lms/getUsers.js index d0420e196..e3be668a4 100644 --- a/src/api/lms/getUsers.js +++ b/src/api/lms/getUsers.js @@ -1,7 +1,11 @@ import queryLMS from '../queryLMS'; -export default async ({ auth }) => queryLMS({ - endpoint: 'getUsers', - method: 'POST', - auth, -}); +export default async ({ auth }) => { + const response = await queryLMS({ + endpoint: 'getUsers', + method: 'POST', + auth, + }); + + return response.body; +}; diff --git a/src/api/lms/removeInstance.js b/src/api/lms/removeInstance.js index 0b0e8ebd2..854268468 100644 --- a/src/api/lms/removeInstance.js +++ b/src/api/lms/removeInstance.js @@ -1,8 +1,12 @@ import queryLMS from '../queryLMS'; -export default async ({ auth, payload }) => queryLMS({ - endpoint: 'removeInstance', - method: 'POST', - payload, - auth, -}); +export default async ({ auth, payload: { customer_id, instance_id } }) => { + const response = await queryLMS({ + endpoint: 'removeInstance', + method: 'POST', + payload: { customer_id, instance_id }, + auth, + }); + + return response.body; +}; diff --git a/src/api/lms/removePaymentMethod.js b/src/api/lms/removePaymentMethod.js index c43dbcef8..1bfddf606 100644 --- a/src/api/lms/removePaymentMethod.js +++ b/src/api/lms/removePaymentMethod.js @@ -1,8 +1,12 @@ import queryLMS from '../queryLMS'; -export default async ({ auth, payload }) => queryLMS({ - endpoint: 'removePaymentMethod', - method: 'POST', - payload, - auth, -}); +export default async ({ auth, payload: { customer_id, stripe_card_id, customer_stripe_id } }) => { + const response = await queryLMS({ + endpoint: 'removePaymentMethod', + method: 'POST', + payload: { customer_id, stripe_card_id, customer_stripe_id }, + auth, + }); + + return response.body; +}; diff --git a/src/api/lms/removeUser.js b/src/api/lms/removeUser.js index 9f3015d49..788710140 100644 --- a/src/api/lms/removeUser.js +++ b/src/api/lms/removeUser.js @@ -1,12 +1,12 @@ import queryLMS from '../queryLMS'; -export default async ({ auth, user_id, customer_id, setLastUpdate }) => { - const result = await queryLMS({ +export default async ({ auth, payload: { user_id, customer_id, setLastUpdate } }) => { + const response = await queryLMS({ endpoint: 'removeUser', method: 'POST', payload: { user_id, customer_id }, auth, }); setLastUpdate(Date.now()); - return result; + return response.body; }; diff --git a/src/api/lms/updateInstance.js b/src/api/lms/updateInstance.js index 04a327802..2778cd187 100644 --- a/src/api/lms/updateInstance.js +++ b/src/api/lms/updateInstance.js @@ -1,8 +1,12 @@ import queryLMS from '../queryLMS'; -export default async ({ auth, instance_name, stripe_product_id, instance_id, customer_id, storage_qty_gb }) => queryLMS({ - endpoint: 'updateInstance', - method: 'POST', - payload: { stripe_product_id, instance_id, customer_id, instance_name, storage_qty_gb }, - auth, -}); +export default async ({ auth, payload: { instance_name, stripe_product_id, instance_id, customer_id, storage_qty_gb } }) => { + const response = await queryLMS({ + endpoint: 'updateInstance', + method: 'POST', + payload: { stripe_product_id, instance_id, customer_id, instance_name, storage_qty_gb }, + auth, + }); + + return response.body; +}; diff --git a/src/api/lms/updateLicense.js b/src/api/lms/updateLicense.js index dbcb69c84..6610c3d47 100644 --- a/src/api/lms/updateLicense.js +++ b/src/api/lms/updateLicense.js @@ -1,8 +1,12 @@ import queryLMS from '../queryLMS'; -export default async ({ auth, payload: { license_id, instance_id, customer_id, stripe_product_id, fingerprint } }) => queryLMS({ - endpoint: 'addLicense', - method: 'POST', - payload: { license_id, instance_id, customer_id, stripe_product_id, fingerprint }, - auth, -}); +export default async ({ auth, payload: { license_id, instance_id, customer_id, stripe_product_id, fingerprint } }) => { + const response = await queryLMS({ + endpoint: 'updateLicense', + method: 'POST', + payload: { license_id, instance_id, customer_id, stripe_product_id, fingerprint }, + auth, + }); + + return response.body; +} diff --git a/src/api/queryLMS.js b/src/api/queryLMS.js index 9fb4b1d66..ce77d0d0d 100644 --- a/src/api/queryLMS.js +++ b/src/api/queryLMS.js @@ -9,22 +9,42 @@ import licenses from '../../mock_data/LMS_API.licenses.json'; // eslint-disable-next-line no-unused-vars export default async ({ endpoint, payload, auth }) => { - /* - const request = await fetch( - `https://api.harperdb.io/v1/${endpoint}`, - { - method: 'POST', - body: JSON.stringify(payload), - headers: { - 'Content-Type': 'application/json', - authorization: `Basic ${btoa(`${auth.user}:${auth.pass}`)}`, + if ((auth.user !== 'test@harperdb.io' || auth.pass !== 'a') && (auth.user !== 'david@harperdb.io' || auth.pass !== 'harperdb')) { + return { + body: { + result: false, + message: 'unknown user or password', }, - }, - ); - return request.json(); - */ + }; + } + + const completedEndpoints = ['addCustomer']; - console.log('Calling API', endpoint); + if (completedEndpoints.includes(endpoint)) { + const request = await fetch( + `https://api.harperdbcloudservices.com/Prod/${endpoint}`, + { + method: 'POST', + body: JSON.stringify(payload), + headers: { + 'Content-Type': 'application/json', + authorization: `Basic ${btoa(`${auth.user}:${auth.pass}`)}`, + }, + }, + ); + const response = await request.json(); + + if (response.errorType) { + const errorObject = JSON.parse(response.errorMessage); + return { + body: { + result: false, + message: errorObject.errorMessage, + }, + }; + } + return response; + } switch (endpoint) { case 'getProducts': @@ -44,24 +64,24 @@ export default async ({ endpoint, payload, auth }) => { case 'getInvoices': return invoices; case 'addLicense': - return { result: true, message: 'Created license', key: '78rfh334ofholhfdoeh3f48hfq', company: 'customer-guid-1' }; + return { statusCode: 200, body: { result: true, message: 'Created license', key: '78rfh334ofholhfdoeh3f48hfq', company: 'customer-guid-1' } }; case 'updateLicense': - return { result: true, message: 'Updated license', key: '78rfh334ofholhfdoeh3f48hfq', company: 'customer-guid-1' }; + return { statusCode: 200, body: { result: true, message: 'Updated license', key: '78rfh334ofholhfdoeh3f48hfq', company: 'customer-guid-1' } }; case 'addTCAcceptance': - return { result: true, message: 'Received Terms and Conditions acceptance' }; + return { statusCode: 200, body: { result: true, message: 'Received Terms and Conditions acceptance' } }; case 'addPaymentMethod': - return { result: true, message: 'Payment method added to account' }; + return { statusCode: 200, body: { result: true, message: 'Payment method added to account' } }; case 'removePaymentMethod': - return { result: true, message: 'Payment method removed from account' }; + return { statusCode: 200, body: { result: true, message: 'Payment method removed from account' } }; case 'removeUser': - return { result: true, message: 'User removed from account' }; + return { statusCode: 200, body: { result: true, message: 'User removed from account' } }; case 'updateInstance': - return { result: true, message: 'Instance is being updated' }; + return { statusCode: 200, body: { result: true, message: 'Instance is being updated' } }; case 'addInstance': - return { result: true, message: 'Instance is being created||added', instance_id: 'instance-guid-1' }; + return { statusCode: 200, body: { result: true, message: 'Instance is being created||added', instance_id: 'instance-guid-1' } }; case 'addUser': - return { result: true, message: 'User created successfully', user_id: 'user-guid-6' }; + return { statusCode: 200, body: { result: true, message: 'User created successfully', user_id: 'user-guid-6' } }; default: - return { error: 'unknown endpoint' }; + return { statusCode: 404, body: { error: 'unknown endpoint' } }; } }; diff --git a/src/assets/styles/_card.scss b/src/assets/styles/_card.scss index 1fad0e6de..1ed076c7b 100644 --- a/src/assets/styles/_card.scss +++ b/src/assets/styles/_card.scss @@ -19,9 +19,8 @@ } } - &.instance, - &.manage { - height: 140px; + &.instance { + height: 150px; cursor: pointer; .instance-name { @@ -37,21 +36,6 @@ } } - &.manage { - height: 49px; - margin-bottom: 1px; - - .card-body { - padding-top: 7px; - padding-bottom: 0; - - .instance-name { - line-height: 1; - padding-top: 3px; - } - } - } - &.no-shadow { box-shadow: none; } diff --git a/src/assets/styles/_csv-input.scss b/src/assets/styles/_csv-input.scss index ee0c910ad..8a43b32fc 100644 --- a/src/assets/styles/_csv-input.scss +++ b/src/assets/styles/_csv-input.scss @@ -9,7 +9,8 @@ width: 100%; text-align: center; - .clear-files { + .clear-files, + .browse-files { z-index: 99999; } } @@ -26,6 +27,7 @@ top: -30px; width: 100%; height: 330px; + background: none !important; } } diff --git a/src/assets/styles/_forms.scss b/src/assets/styles/_forms.scss index a04ce56a5..6fff12a51 100644 --- a/src/assets/styles/_forms.scss +++ b/src/assets/styles/_forms.scss @@ -52,6 +52,7 @@ input:-webkit-autofill:active { overflow: hidden; display: inline-block; width: 170px; + font-size: 12px; @media screen and (max-width: 575px) { width: 160px !important; @@ -158,8 +159,9 @@ textarea { overflow-x: hidden; } -.new-instance-label { +.fieldset-label { font-weight: 700; + font-size: 12px; margin-bottom: 5px; } @@ -187,6 +189,17 @@ textarea { display: none; } +#add-customer-background { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: #fff; + height: 100%; + width: 100%; +} + diff --git a/src/assets/styles/_login.scss b/src/assets/styles/_login.scss index 548d2d679..9604a078e 100644 --- a/src/assets/styles/_login.scss +++ b/src/assets/styles/_login.scss @@ -26,7 +26,7 @@ input { text-align: center !important; background: $white-overlay !important; - color: $color-white !important; + color: $color-black !important; border: 0 !important; &::placeholder { diff --git a/src/assets/styles/_react-table.scss b/src/assets/styles/_react-table.scss index 8de85478d..50f5c021f 100755 --- a/src/assets/styles/_react-table.scss +++ b/src/assets/styles/_react-table.scss @@ -7,6 +7,7 @@ .rt-thead { &.-header { border-top: 1px solid rgba(0, 0, 0, .05) !important; + font-weight: 700; } &.-filters { diff --git a/src/components/account/billing/cardEdit.js b/src/components/account/billing/cardEdit.js index 8c1dc427f..f2139d2ad 100644 --- a/src/components/account/billing/cardEdit.js +++ b/src/components/account/billing/cardEdit.js @@ -6,13 +6,16 @@ import { useLocation, useHistory } from 'react-router-dom'; import queryString from 'query-string' import cardOptions from '../../../util/stripe/cardOptions'; -import useLMS from '../../../state/stores/lmsData'; -import defaultLMSData from '../../../state/defaults/defaultLMSData'; +import useLMS from '../../../state/stores/lmsAuth'; +import defaultLMSAuth from '../../../state/defaults/defaultLMSAuth'; import addPaymentMethod from '../../../api/lms/addPaymentMethod'; import getCustomer from '../../../api/lms/getCustomer'; +import useApp from '../../../state/stores/appData'; +import defaultAppData from '../../../state/defaults/defaultAppData'; export default ({ setEditingCard, customerCard }) => { - const [lmsData, setLMSData] = useLMS(defaultLMSData); + const [lmsAuth] = useLMS(defaultLMSAuth); + const [appData, setAppData] = useApp(defaultAppData); const [postalCode, setPostalCode] = useState(false); const [cardSubmitted, setCardSubmitted] = useState(false); const [error, setError] = useState(null); @@ -34,9 +37,9 @@ export default ({ setEditingCard, customerCard }) => { if (payload.error) { setError(payload.error); } else { - await addPaymentMethod({ payment_method_id: payload.paymentMethod.id, stripe_customer_id: lmsData.customer.stripe_customer_object.id, auth: lmsData.auth }); - const customer = await getCustomer({ auth: lmsData.auth }); - setLMSData({ ...lmsData, customer }); + await addPaymentMethod({ auth: lmsAuth, payload: { payment_method_id: payload.paymentMethod.id, stripe_customer_id: appData.customer.stripe_customer_object.id } }); + const customer = await getCustomer({ auth: lmsAuth }); + setAppData({ ...appData, customer }); setEditingCard(false); if (returnURL) { setTimeout(() => history.push(returnURL), 100); @@ -119,7 +122,7 @@ export default ({ setEditingCard, customerCard }) => { )} {error && ( -
+

{error.message}
diff --git a/src/components/account/billing/index.js b/src/components/account/billing/index.js index 95b3bd83d..629a30a2a 100644 --- a/src/components/account/billing/index.js +++ b/src/components/account/billing/index.js @@ -2,16 +2,19 @@ import React, { useState } from 'react'; import { Row, Col, Card, CardBody } from '@nio/ui-kit'; import customerHasChargeableCard from '../../../util/stripe/customerHasChargeableCard'; -import useLMS from '../../../state/stores/lmsData'; -import defaultLMSData from '../../../state/defaults/defaultLMSData'; +import useLMS from '../../../state/stores/lmsAuth'; +import defaultLMSAuth from '../../../state/defaults/defaultLMSAuth'; import StaticCard from './cardStatic'; import EditCard from './cardEdit'; import Invoices from './invoices'; +import useApp from '../../../state/stores/appData'; +import defaultAppData from '../../../state/defaults/defaultAppData'; export default () => { - const [lmsData] = useLMS(defaultLMSData); + const [lmsAuth] = useLMS(defaultLMSAuth); + const [appData] = useApp(defaultAppData); const [editingCard, setEditingCard] = useState(false); - const customerCard = customerHasChargeableCard(lmsData.customer); + const customerCard = customerHasChargeableCard(appData.customer); return ( @@ -26,11 +29,11 @@ export default () => { /> ) : ( )} diff --git a/src/components/account/billing/invoices.js b/src/components/account/billing/invoices.js index 2dccd1bd4..442a9c3b2 100644 --- a/src/components/account/billing/invoices.js +++ b/src/components/account/billing/invoices.js @@ -3,15 +3,15 @@ import { Row, Col } from '@nio/ui-kit'; import useAsyncEffect from 'use-async-effect'; import getInvoices from '../../../api/lms/getInvoices'; -import useLMS from '../../../state/stores/lmsData'; -import defaultLMSData from '../../../state/defaults/defaultLMSData'; +import useLMS from '../../../state/stores/lmsAuth'; +import defaultLMSAuth from '../../../state/defaults/defaultLMSAuth'; export default () => { - const [lmsData] = useLMS(defaultLMSData); + const [lmsAuth] = useLMS(defaultLMSAuth); const [customerInvoices, setCustomerInvoices] = useState([]); useAsyncEffect(async () => { - const newCustomerInvoices = await getInvoices({ auth: lmsData.auth }); + const newCustomerInvoices = await getInvoices({ auth: lmsAuth }); setCustomerInvoices(newCustomerInvoices); }, []); @@ -19,7 +19,7 @@ export default () => {
- {new Date(i.invoice_date).toLocaleString()} + {new Date(i.created * 1000).toLocaleString()} $ diff --git a/src/components/account/profile/password.js b/src/components/account/profile/password.js index 520c607f1..a45ac782a 100644 --- a/src/components/account/profile/password.js +++ b/src/components/account/profile/password.js @@ -2,10 +2,10 @@ import React, { useState } from 'react'; import { Row, Col, Input, Button } from '@nio/ui-kit'; import useAsyncEffect from 'use-async-effect'; -import defaultProfileFormData from '../../../state/defaults/defaultProfileFormData'; +import defaultPasswordFormData from '../../../state/defaults/defaultPasswordFormData'; export default () => { - const [passwordForm, updatePasswordForm] = useState(defaultProfileFormData); + const [passwordForm, updatePasswordForm] = useState(defaultPasswordFormData); useAsyncEffect(async () => { if (passwordForm.submitted) { diff --git a/src/components/account/users/add.js b/src/components/account/users/add.js index 8dc934c44..296f7520f 100644 --- a/src/components/account/users/add.js +++ b/src/components/account/users/add.js @@ -6,8 +6,11 @@ import { useAlert } from 'react-alert'; import addUser from '../../../api/lms/addUser'; import defaultUserFormData from '../../../state/defaults/defaultUserFormData'; import isEmail from '../../../util/isEmail'; +import useLMS from '../../../state/stores/lmsAuth'; +import defaultLMSAuth from '../../../state/defaults/defaultLMSAuth'; export default ({ setLastUpdate, customerId }) => { + const [lmsAuth] = useLMS(defaultLMSAuth); const alert = useAlert(); const [userForm, updateUserForm] = useState(defaultUserFormData); @@ -17,7 +20,7 @@ export default ({ setLastUpdate, customerId }) => { if (!userForm.customer_id || !userForm.firstname || !userForm.lastname || !isEmail(userForm.email)) { updateUserForm({ ...userForm, submitted: false, error: 'All fields must be filled out' }); } else { - const response = await addUser(userForm); + const response = await addUser({ auth: lmsAuth, payload: userForm }); if (response.result) { updateUserForm(defaultUserFormData); setLastUpdate(Date.now()); @@ -34,33 +37,39 @@ export default ({ setLastUpdate, customerId }) => { add user - updateUserForm({ ...userForm, firstname: e.target.value, error: false })} - /> -
- updateUserForm({ ...userForm, lastname: e.target.value, error: false })} - /> -
- updateUserForm({ ...userForm, email: e.target.value, error: false })} - /> -
+
first name
+
+ updateUserForm({ ...userForm, firstname: e.target.value, error: false })} + /> +
+ +
last name
+
+ updateUserForm({ ...userForm, lastname: e.target.value, error: false })} + /> +
+ +
email address
+
+ updateUserForm({ ...userForm, email: e.target.value, error: false })} + /> +
+ +
) : status === 'processing' ? (
pre-processing {commaNumbers(newRecordCount)} records
) : fileError ? (
{fileError}
) : ( -
Click to select or drag and drop a .csv file to insert into {schema}.{table}
+
+ Click to select or drag and drop a .csv file to insert into {schema}.{table}
+ +
)}
{ !status && ( diff --git a/src/components/instance/clustering/datatable.js b/src/components/instance/clustering/datatable.js index 15b1846e5..4d253593d 100644 --- a/src/components/instance/clustering/datatable.js +++ b/src/components/instance/clustering/datatable.js @@ -24,7 +24,7 @@ export default ({ instances, network, schema, table, auth, refreshInstance }) => return { id, instance_name, is_ssl, host, port, url, clusterPort, publish, subscribe, connection, channel, subscriptions }; }); setTableData({ ...tableData, data: newTableData }); - setTableState({ ...tableState, filtered: [], sorted: [{ id: 'name', desc: false }], page: 0 }); + setTableState({ ...tableState, filtered: [], sorted: [{ id: 'instance_name', desc: false }], page: 0 }); } }, [table]); diff --git a/src/components/instance/clustering/manage.js b/src/components/instance/clustering/manage.js index 1b8767383..14fa01476 100644 --- a/src/components/instance/clustering/manage.js +++ b/src/components/instance/clustering/manage.js @@ -5,15 +5,15 @@ import { useHistory } from 'react-router'; import useAsyncEffect from 'use-async-effect'; import EntityManager from '../../shared/entityManager'; -import useLMS from '../../../state/stores/lmsData'; import DataTable from './datatable'; import buildInstanceStructure from '../../../util/buildInstanceStructure'; import handleSchemaTableRedirect from '../../../util/handleSchemaTableRedirect'; -import defaultLMSData from '../../../state/defaults/defaultLMSData'; +import useApp from '../../../state/stores/appData'; +import defaultAppData from '../../../state/defaults/defaultAppData'; export default ({ auth, network, refreshInstance, structure }) => { const history = useHistory(); - const [lmsData] = useLMS(defaultLMSData); + const [appData] = useApp(defaultAppData); const { instance_id, schema, table } = useParams(); const [entities, setEntities] = useState({ schemas: [], tables: [] }); @@ -50,7 +50,7 @@ export default ({ auth, network, refreshInstance, structure }) => { i.id !== instance_id)} + instances={appData.instances.filter((i) => i.id !== instance_id)} network={network} schema={schema} table={table} diff --git a/src/components/instance/clustering/setup_port.js b/src/components/instance/clustering/setup_port.js index 6494d8d3b..d035df952 100644 --- a/src/components/instance/clustering/setup_port.js +++ b/src/components/instance/clustering/setup_port.js @@ -7,7 +7,7 @@ import defaultFormData from '../../../state/defaults/defaultClusterFormData'; export default ({ port, setPort }) => { const alert = useAlert(); - const [portFormData, updatePortForm] = useState({ ...defaultFormData }); + const [portFormData, updatePortForm] = useState(defaultFormData); useAsyncEffect(async () => { const { submitted, newPort } = portFormData; diff --git a/src/components/instance/clustering/setup_user.js b/src/components/instance/clustering/setup_user.js index 8d8f893b5..1ac6be243 100644 --- a/src/components/instance/clustering/setup_user.js +++ b/src/components/instance/clustering/setup_user.js @@ -8,7 +8,7 @@ import createClusterUser from '../../../api/instance/createClusterUser'; export default ({ clusterUser, clusterRole, auth, refreshInstance }) => { const alert = useAlert(); - const [userFormData, updateUserForm] = useState({ ...defaultFormData }); + const [userFormData, updateUserForm] = useState(defaultFormData); useAsyncEffect(async () => { const { submitted, username, password } = userFormData; diff --git a/src/components/instance/config/index.js b/src/components/instance/config/index.js index 0438739fe..3ff163315 100644 --- a/src/components/instance/config/index.js +++ b/src/components/instance/config/index.js @@ -1,11 +1,11 @@ import React from 'react'; import UpdateInstanceForm from './updateInstanceForm'; -import useLMS from '../../../state/stores/lmsData'; -import defaultLMSData from '../../../state/defaults/defaultLMSData'; +import useApp from '../../../state/stores/appData'; +import defaultAppData from '../../../state/defaults/defaultAppData'; export default ({ auth, details, refreshInstance }) => { - const [{ products }] = useLMS(defaultLMSData); + const [{ products }] = useApp(defaultAppData); return ( <> diff --git a/src/components/instance/config/updateInstanceForm.js b/src/components/instance/config/updateInstanceForm.js index 5147b1d5a..cc5673e6a 100644 --- a/src/components/instance/config/updateInstanceForm.js +++ b/src/components/instance/config/updateInstanceForm.js @@ -3,20 +3,22 @@ import { Button, Card, CardBody, Col, Input, RadioCheckbox, Row } from '@nio/ui- import useAsyncEffect from 'use-async-effect'; import { useHistory } from 'react-router'; -import useLMS from '../../../state/stores/lmsData'; +import useLMS from '../../../state/stores/lmsAuth'; import updateInstance from '../../../api/lms/updateInstance'; import updateLicense from '../../../api/lms/updateLicense'; import setLicense from '../../../api/instance/setLicense'; -import defaultLMSData from '../../../state/defaults/defaultLMSData'; +import defaultLMSAuth from '../../../state/defaults/defaultLMSAuth'; import customerHasChargeableCard from '../../../util/stripe/customerHasChargeableCard'; +import useApp from '../../../state/stores/appData'; +import defaultAppData from '../../../state/defaults/defaultAppData'; export default ({ instanceAuth, details, refreshInstance, computeProducts, storageProducts }) => { - const [{ auth, customer, regions }] = useLMS(defaultLMSData); + const [lmsAuth] = useLMS(defaultLMSAuth); + const [{ customer }] = useApp(defaultAppData); const history = useHistory(); const [formState, setFormState] = useState({ submitted: false, error: false }); const [formData, updateForm] = useState({ instance_name: details.instance_name, stripe_plan_id: details.stripe_plan_id, storage_qty_gb: details.storage_qty_gb }); const hasCard = customerHasChargeableCard(customer); - const thisRegion = details.instance_region && regions && regions.find((r) => r.value === details.instance_region); let totalPrice = 0; const newComputePrice = computeProducts.find((p) => p.value === formData.stripe_plan_id); @@ -34,9 +36,9 @@ export default ({ instanceAuth, details, refreshInstance, computeProducts, stora if (submitted) { const { stripe_product_id, instance_name, instance_id, customer_id, license_id, fingerprint, storage_qty_gb } = formData; - const newLicense = await updateLicense({ auth, payload: { license_id, stripe_product_id, instance_id, customer_id, fingerprint } }); + const newLicense = await updateLicense({ auth: lmsAuth, payload: { license_id, stripe_product_id, instance_id, customer_id, fingerprint } }); await setLicense({ auth: instanceAuth, key: newLicense.key, company: newLicense.company }); - await updateInstance({ auth, payload: { stripe_product_id, instance_id, customer_id, instance_name, storage_qty_gb } }); + await updateInstance({ auth: lmsAuth, payload: { stripe_product_id, instance_id, customer_id, instance_name, storage_qty_gb } }); setFormState({ submitted: false }); refreshInstance(Date.now()); } @@ -45,7 +47,7 @@ export default ({ instanceAuth, details, refreshInstance, computeProducts, stora return ( -
Instance Name
+
Instance Name
updateForm({ ...formData, instance_name: e.target.value, error: false })} @@ -55,18 +57,18 @@ export default ({ instanceAuth, details, refreshInstance, computeProducts, stora />
- {thisRegion && ( + {details.region && ( <> -
Instance Region (no modification)
+
Instance Region (no modification)
- {thisRegion.label} + {details.region.label}
)} {storageProducts && ( <> -
Storage Size
+
Storage Size
updateForm({ ...formData, storage_qty_gb: value })} options={storageProducts} value={formData.storage_qty_gb} - defaultValue={details.storage_qty_gb ? storageProducts.find((p) => p.value === details.storage_qty_gb) : storageProducts[0]} + defaultValue={details.storage} />
)} -
Instance Type
+
Instance Type
{computeProducts && ( updateForm({ ...formData, stripe_plan_id: value })} options={computeProducts} value={formData.stripe_plan_id} - defaultValue={details.stripe_plan_id ? computeProducts.find((p) => p.value === details.stripe_plan_id) : computeProducts[0]} + defaultValue={details.compute} /> )}
@@ -103,7 +105,7 @@ export default ({ instanceAuth, details, refreshInstance, computeProducts, stora {!hasChanged ? 'Current' : 'New'} Price - ${totalPrice.toFixed(2)}/{computeProducts[0].interval} + ${totalPrice.toFixed(2)}/{details.compute.interval} diff --git a/src/components/instance/index.js b/src/components/instance/index.js index dfa701451..fe6a23be2 100644 --- a/src/components/instance/index.js +++ b/src/components/instance/index.js @@ -4,29 +4,37 @@ import useAsyncEffect from 'use-async-effect'; import SubNav from '../navs/subnav'; import routes from './routes'; -import useLMS from '../../state/stores/lmsData'; +import useLMS from '../../state/stores/lmsAuth'; import useInstanceAuth from '../../state/stores/instanceAuths'; import buildActiveInstanceObject from '../../util/buildActiveInstanceObject'; -import defaultLMSData from '../../state/defaults/defaultLMSData'; +import defaultLMSAuth from '../../state/defaults/defaultLMSAuth'; import defaultActiveInstance from '../../state/defaults/defaultActiveInstance'; import getInstances from '../../api/lms/getInstances'; import getLicenses from '../../api/lms/getLicenses'; +import useApp from '../../state/stores/appData'; +import defaultAppData from '../../state/defaults/defaultAppData'; import getProducts from '../../api/lms/getProducts'; +import getRegions from '../../api/lms/getRegions'; +import getCustomer from '../../api/lms/getCustomer'; export default () => { const { instance_id } = useParams(); - const [lmsData, setLMSData] = useLMS(defaultLMSData); + const [lmsAuth] = useLMS(defaultLMSAuth); + const [appData, setAppData] = useApp(defaultAppData); const [instanceAuths] = useInstanceAuth({}); const [activeInstance, setActiveInstance] = useState(defaultActiveInstance); const [lastUpdate, refreshInstance] = useState(false); useAsyncEffect(async () => { if (instance_id) { - const instances = await getInstances({ auth: lmsData.auth }); - const licenses = await getLicenses({ auth: lmsData.auth }); - const products = await getProducts({ auth: lmsData.auth }); - setLMSData({ ...lmsData, instances, licenses, products }); - const activeInstanceObject = await buildActiveInstanceObject({ instance_id, instanceAuths, lmsData: { ...lmsData, instances, licenses, products } }); + const products = await getProducts(); + const regions = await getRegions(); + const customer = await getCustomer({ auth: lmsAuth }); + const licenses = await getLicenses({ auth: lmsAuth }); + const instances = await getInstances({ auth: lmsAuth, products, regions, licenses }); + const newAppData = { ...appData, customer, products, regions, instances, licenses }; + setAppData(newAppData); + const activeInstanceObject = await buildActiveInstanceObject({ instance_id, instanceAuths, appData: newAppData }); if (!activeInstanceObject.error) { setActiveInstance(activeInstanceObject); } diff --git a/src/components/instance/users/add.js b/src/components/instance/users/add.js new file mode 100644 index 000000000..48fe3b510 --- /dev/null +++ b/src/components/instance/users/add.js @@ -0,0 +1,98 @@ +import React, { useState } from 'react'; +import { Input, Button, Card, CardBody, RadioCheckbox } from '@nio/ui-kit'; +import useAsyncEffect from 'use-async-effect'; +import { useAlert } from 'react-alert'; + +import addUser from '../../../api/instance/addUser'; + +export default ({ auth, roles, users, refreshInstance }) => { + const alert = useAlert(); + const [formState, setFormState] = useState({ submitted: false, error: false }); + const [formData, updateForm] = useState({ username: '', password: '' }); + + useAsyncEffect(async () => { + const { submitted } = formState; + if (submitted) { + const { username, password, role } = formData; + + if (!username || !role || !password) { + setFormState({ submitted: false, error: 'All fields must be filled out' }); + } else if (username.indexOf(' ') !== -1) { + setFormState({ submitted: false, error: 'Username may not have spaces' }); + } else if (users.find((u) => u.username.toLowerCase() === username.toLowerCase())) { + setFormState({ submitted: false, error: 'User already exists' }); + } else { + const response = await addUser({ auth, role, username, password }); + if (response.message.indexOf('successfully') !== -1) { + updateForm({ username: '', password: '', role: roles[0].id }); + refreshInstance(Date.now()); + alert.success(response.message); + } else { + alert.error(response.message); + } + setFormState({ submitted: false, error: false }); + } + } + }, [formState]); + + useAsyncEffect(() => { if (roles) updateForm({ ...formData, role: roles[0].id }); }, [roles]); + + return ( + <> + add user + + +
username
+
+ updateForm({ ...formData, username: e.target.value, error: false })} + /> +
+ +
password
+
+ updateForm({ ...formData, password: e.target.value, error: false })} + /> +
+ +
role
+
+ {roles && ( + updateForm({ ...formData, role: value })} + options={roles ? roles.map((r) => ({ label: r.role, value: r.id })) : []} + value={formData.role} + defaultValue={formData.role} + /> + )} +
+ + + {formState.error && ( +
+
+ {formState.error} +
+ )} +
+
+ + ); +}; diff --git a/src/components/instance/users/datatable.js b/src/components/instance/users/datatable.js new file mode 100644 index 000000000..9b2b6efed --- /dev/null +++ b/src/components/instance/users/datatable.js @@ -0,0 +1,44 @@ +import React, { useState } from 'react'; +import { Card, CardBody, Row, Col } from '@nio/ui-kit'; +import ReactTable from 'react-table'; + +import defaultTableState from '../../../state/defaults/defaultTableState'; + +export default ({ tableData }) => { + const [tableState, setTableState] = useState({ ...defaultTableState, sorted: [{ id: 'username', desc: false }] }); + + return ( + <> + + + existing users + + + setTableState({ ...tableState, filtered: tableState.showFilter ? [] : tableState.filtered, showFilter: !tableState.showFilter })}> + + + + + + + setTableState({ ...tableState, filtered: value })} + filtered={tableState.filtered} + onSortedChange={(value) => setTableState({ ...tableState, sorted: value })} + sorted={tableState.sorted} + onPageChange={(value) => setTableState({ ...tableState, page: value })} + page={tableState.page} + filterable={tableState.showFilter} + defaultPageSize={tableState.pageSize} + pageSize={tableState.pageSize} + onPageSizeChange={(value) => setTableState({ ...tableState, pageSize: value })} + resizable={false} + /> + + + + ); +}; diff --git a/src/components/instance/users/index.js b/src/components/instance/users/index.js index cf7ec72f4..d9f52b9b8 100644 --- a/src/components/instance/users/index.js +++ b/src/components/instance/users/index.js @@ -1,15 +1,42 @@ -import React from 'react'; -import { Card, CardBody, Row, Col } from '@nio/ui-kit'; - -export default () => ( - - - users - - - content here - - - - -); +import React, { useState, useEffect } from 'react'; +import { Row, Col } from '@nio/ui-kit'; +import { useAlert } from 'react-alert'; + +import DataTable from './datatable'; +import AddUserForm from './add'; + +import dropUser from '../../../api/instance/dropUser'; +import instanceUserColumns from '../../../util/datatable/instanceUserColumns'; + +export default ({ auth, users, roles, refreshInstance }) => { + const alert = useAlert(); + const [tableData, setTableData] = useState({ data: [], columns: [] }); + + const deleteUser = async ({ username }) => { + const response = await dropUser({ auth, username }); + + if (response.message.indexOf('successfully') !== -1) { + alert.success(response.message); + setTimeout(() => refreshInstance(Date.now()), 0); + } else { + alert.error(response.message); + } + }; + + useEffect(() => { + if (users && auth) { + setTableData({ data: users, columns: instanceUserColumns({ deleteUser }) }); + } + }, [users, auth]); + + return ( + + + + + + + + + ); +}; diff --git a/src/components/instances/index.js b/src/components/instances/index.js index 4a1383880..a88bb6270 100644 --- a/src/components/instances/index.js +++ b/src/components/instances/index.js @@ -3,39 +3,45 @@ import { Row } from '@nio/ui-kit'; import useAsyncEffect from 'use-async-effect'; import useInstanceAuth from '../../state/stores/instanceAuths'; +import useApp from '../../state/stores/appData'; +import useLMS from '../../state/stores/lmsAuth'; + +import defaultAppData from '../../state/defaults/defaultAppData'; +import defaultLMSAuth from '../../state/defaults/defaultLMSAuth'; + import InstanceCard from './list/instanceCard'; import NewInstanceCard from './list/newInstanceCard'; -import useLMS from '../../state/stores/lmsData'; -import defaultLMSData from '../../state/defaults/defaultLMSData'; +import SubNav from '../navs/subnav'; + import getInstances from '../../api/lms/getInstances'; import getLicenses from '../../api/lms/getLicenses'; +import filterInstances from '../../util/filterInstances'; +import getCustomer from '../../api/lms/getCustomer'; import getProducts from '../../api/lms/getProducts'; import getRegions from '../../api/lms/getRegions'; -import filterInstances from '../../util/filterInstances'; -import SubNav from '../navs/subnav'; export default () => { + const [lmsAuth] = useLMS(defaultLMSAuth); const [instanceAuths, setInstanceAuths] = useInstanceAuth({}); - const [lmsData, setLMSData] = useLMS(defaultLMSData); + const [appData, setAppData] = useApp(defaultAppData); const [search, setSearch] = useState(''); const [local, setLocal] = useState(true); const [cloud, setCloud] = useState(true); const [filteredInstances, setFilteredInstances] = useState([]); + useAsyncEffect(() => { + setFilteredInstances(filterInstances({ local, cloud, search, instances: appData.instances })); + }, [search, local, cloud, appData.instances]); + useAsyncEffect(async () => { - const instances = await getInstances({ auth: lmsData.auth }); - const licenses = await getLicenses({ auth: lmsData.auth }); - const products = await getProducts({ auth: lmsData.auth }); - const regions = await getRegions({ auth: lmsData.auth }); - setLMSData({ ...lmsData, instances, licenses, products, regions }); + const products = await getProducts(); + const regions = await getRegions(); + const customer = await getCustomer({ auth: lmsAuth }); + const licenses = await getLicenses({ auth: lmsAuth }); + const instances = await getInstances({ auth: lmsAuth, products, regions, licenses }); + setAppData({ ...appData, customer, products, regions, instances, licenses }); }, []); - useAsyncEffect(() => { - if (lmsData.instances.length) { - setFilteredInstances(filterInstances({ local, cloud, search, instances: lmsData.instances })); - } - }, [search, local, cloud, lmsData.instances]); - return ( <> { /> - {filteredInstances.map((i) => ( + {filteredInstances?.map((i) => ( p.value === i.stripe_plan_id)} - storage={i.is_local ? { disk_space: 'n/a' } : lmsData.products && lmsData.products.cloudStorage.find((p) => p.value === i.storage_qty_gb)} - hasAuth={instanceAuths[i.id]} + hasAuth={instanceAuths && instanceAuths[i.id]} setAuth={({ id, user, pass }) => setInstanceAuths({ ...instanceAuths, [id]: user && pass ? { user, pass } : false })} /> ))} diff --git a/src/components/instances/list/cardFront.js b/src/components/instances/list/cardFront.js index f59da98a7..9298bdfb0 100644 --- a/src/components/instances/list/cardFront.js +++ b/src/components/instances/list/cardFront.js @@ -22,7 +22,7 @@ export default ({ id, instance_name, host, port, is_ssl, is_local, flipCard, set )} -
+
URL @@ -35,7 +35,8 @@ export default ({ id, instance_name, host, port, is_ssl, is_local, flipCard, set {compute ? compute.ram : }
STORAGE - {storage? storage.disk_space : } + {storage ? storage.disk_space : 'n/a'} +
diff --git a/src/components/instances/list/instanceCard.js b/src/components/instances/list/instanceCard.js index a063699e1..07fd71e9d 100644 --- a/src/components/instances/list/instanceCard.js +++ b/src/components/instances/list/instanceCard.js @@ -7,7 +7,6 @@ import CardBack from './cardBack'; export default (props) => { const [flipState, setFlipState] = useState(false); - return ( diff --git a/src/components/instances/list/newInstanceCard.js b/src/components/instances/list/newInstanceCard.js index 4e06a7891..ebfefcea8 100644 --- a/src/components/instances/list/newInstanceCard.js +++ b/src/components/instances/list/newInstanceCard.js @@ -1,7 +1,7 @@ import React, { useState } from 'react'; import { Card, CardBody, Col } from '@nio/ui-kit'; -import NewInstanceModal from '../new/modal'; +import NewInstanceModal from '../new'; export default () => { const [showForm, setShowForm] = useState(false); diff --git a/src/components/instances/new/cloud.js b/src/components/instances/new/cloud.js index 73a0ad9e9..ed1150d2f 100644 --- a/src/components/instances/new/cloud.js +++ b/src/components/instances/new/cloud.js @@ -34,7 +34,7 @@ export default ({ products, storage, regions, hasCard, newInstance, setNewInstan <> -
Storage Size (scroll for more)
+
Storage Size (scroll for more)
-
Instance RAM (scroll for more)
+
Instance RAM (scroll for more)
-
Instance Region (scroll for more)
+
Instance Region (scroll for more)
{formState.error && ( -
+

{formState.error}
diff --git a/src/components/instances/new/confirm.js b/src/components/instances/new/confirm.js index 10a94ac9c..82f4e5c9e 100644 --- a/src/components/instances/new/confirm.js +++ b/src/components/instances/new/confirm.js @@ -3,9 +3,12 @@ import { Col, Row, Button, Card, CardBody } from '@nio/ui-kit'; import useAsyncEffect from 'use-async-effect'; import addInstance from '../../../api/lms/addInstance'; +import useInstanceAuth from '../../../state/stores/instanceAuths'; -export default ({ newInstance, computeProduct, storageProduct, setShowForm, setPurchaseStep }) => { +export default ({ newInstance, computeProduct, storageProduct, setShowForm, setPurchaseStep, lmsAuth }) => { const [formData, updateForm] = useState({ submitted: false, error: false }); + const [instanceAuths, setInstanceAuths] = useInstanceAuth({}); + let totalPrice = 0; if (computeProduct.price !== 'FREE') totalPrice += parseFloat(computeProduct.price); if (storageProduct.price !== 'FREE') totalPrice += parseFloat(storageProduct.price); @@ -13,12 +16,15 @@ export default ({ newInstance, computeProduct, storageProduct, setShowForm, setP useAsyncEffect(async () => { const { submitted } = formData; if (submitted) { - console.log(newInstance); + const newInstanceAuth = { user: newInstance.user, pass: newInstance.pass }; + delete newInstance.user; + delete newInstance.pass; - const response = await addInstance(newInstance); + const response = await addInstance({ auth: lmsAuth, payload: { ...newInstance } }); if (response.result) { + setInstanceAuths({ ...instanceAuths, [response.instance_id]: newInstanceAuth }); updateForm({ submitted: false, error: false }); - setShowForm(false); + setTimeout(() => setShowForm(false), 0); } else { updateForm({ submitted: false, error: response.message }); } diff --git a/src/components/instances/new/modal.js b/src/components/instances/new/index.js similarity index 73% rename from src/components/instances/new/modal.js rename to src/components/instances/new/index.js index 195544df5..8fe027273 100644 --- a/src/components/instances/new/modal.js +++ b/src/components/instances/new/index.js @@ -1,30 +1,26 @@ import React, { useState } from 'react'; import { Modal, ModalHeader, ModalBody, Loader } from '@nio/ui-kit'; -import useAsyncEffect from 'use-async-effect'; -import useLMS from '../../../state/stores/lmsData'; -import defaultLMSData from '../../../state/defaults/defaultLMSData'; import customerHasChargeableCard from '../../../util/stripe/customerHasChargeableCard'; -import getCustomer from '../../../api/lms/getCustomer'; - import steps from '../../../util/addInstanceSteps'; + import InstanceTypeForm from './type'; import InstanceMetadataForm from './meta'; import LocalInstanceForm from './local'; import CloudInstanceForm from './cloud'; import CustomerPaymentForm from './payment'; import ConfirmOrderForm from './confirm'; +import useLMS from '../../../state/stores/lmsAuth'; +import defaultLMSAuth from '../../../state/defaults/defaultLMSAuth'; +import useApp from '../../../state/stores/appData'; +import defaultAppData from '../../../state/defaults/defaultAppData'; export default ({ setShowForm }) => { - const [lmsData, setLMSData] = useLMS(defaultLMSData); + const [lmsAuth] = useLMS(defaultLMSAuth); + const [appData] = useApp(defaultAppData); const [purchaseStep, setPurchaseStep] = useState('type'); const [newInstance, setNewInstance] = useState({}); - const hasCard = customerHasChargeableCard(lmsData.customer); - - useAsyncEffect(async () => { - const customer = await getCustomer({ auth: lmsData.auth }); - setLMSData({ ...lmsData, customer }); - }, []); + const hasCard = customerHasChargeableCard(appData.customer); return ( setShowForm(false)}> @@ -32,7 +28,7 @@ export default ({ setShowForm }) => { {steps[purchaseStep].label} - {!lmsData.products ? ( + {!appData.products ? ( ) : purchaseStep === 'type' ? ( { /> ) : purchaseStep === 'details_local' ? ( { /> ) : purchaseStep === 'details_cloud' ? ( { /> ) : purchaseStep === 'payment' ? ( p.value === newInstance.stripe_plan_id)} - storageProduct={newInstance.is_local ? { price: 'FREE' } : lmsData.products.cloudStorage.find((p) => p.value === newInstance.storage_qty_gb)} + computeProduct={appData.products[newInstance.is_local ? 'localCompute' : 'cloudCompute'].find((p) => p.value === newInstance.stripe_plan_id)} + storageProduct={newInstance.is_local ? { price: 'FREE' } : appData.products.cloudStorage.find((p) => p.value === newInstance.storage_qty_gb)} setPurchaseStep={setPurchaseStep} /> ) : purchaseStep === 'confirm' ? ( p.value === newInstance.stripe_plan_id)} - storageProduct={newInstance.is_local ? { price: 'FREE' } : lmsData.products.cloudStorage.find((p) => p.value === newInstance.storage_qty_gb)} + computeProduct={appData.products[newInstance.is_local ? 'localCompute' : 'cloudCompute'].find((p) => p.value === newInstance.stripe_plan_id)} + storageProduct={newInstance.is_local ? { price: 'FREE' } : appData.products.cloudStorage.find((p) => p.value === newInstance.storage_qty_gb)} setShowForm={setShowForm} setPurchaseStep={setPurchaseStep} /> diff --git a/src/components/instances/new/local.js b/src/components/instances/new/local.js index 9207419f5..28c8496b6 100644 --- a/src/components/instances/new/local.js +++ b/src/components/instances/new/local.js @@ -26,7 +26,7 @@ export default ({ products, hasCard, newInstance, setNewInstance, setPurchaseSte <> -
Instance Type
+
Instance Type
{formState.error && ( -
+

{formState.error}
diff --git a/src/components/instances/new/meta.js b/src/components/instances/new/meta.js index b6c14993c..c32aa3d21 100644 --- a/src/components/instances/new/meta.js +++ b/src/components/instances/new/meta.js @@ -37,7 +37,7 @@ export default ({ newInstance, setNewInstance, setPurchaseStep }) => { <> -
Instance Name
+
Instance Name
@@ -54,7 +54,7 @@ export default ({ newInstance, setNewInstance, setPurchaseStep }) => {
-
Admin Credentials
+
Admin Credentials
@@ -87,7 +87,7 @@ export default ({ newInstance, setNewInstance, setPurchaseStep }) => { {newInstance.is_local && ( <> -
Instance Details
+
Instance Details
@@ -162,7 +162,7 @@ export default ({ newInstance, setNewInstance, setPurchaseStep }) => { {formState.error && ( -
+

{formState.error}
diff --git a/src/components/instances/new/payment.js b/src/components/instances/new/payment.js index 8e4dbbfc5..e5e02ff86 100644 --- a/src/components/instances/new/payment.js +++ b/src/components/instances/new/payment.js @@ -6,11 +6,11 @@ import useAsyncEffect from 'use-async-effect'; import cardOptions from '../../../util/stripe/cardOptions'; import addPaymentMethod from '../../../api/lms/addPaymentMethod'; import getCustomer from '../../../api/lms/getCustomer'; -import useLMS from '../../../state/stores/lmsData'; -import defaultLMSData from '../../../state/defaults/defaultLMSData'; +import useApp from '../../../state/stores/appData'; +import defaultAppData from '../../../state/defaults/defaultAppData'; -export default ({ hasCard, computeProduct, newInstance, storageProduct, setPurchaseStep }) => { - const [lmsData, setLMSData] = useLMS(defaultLMSData); +export default ({ hasCard, computeProduct, newInstance, storageProduct, setPurchaseStep, lmsAuth }) => { + const [appData, setAppData] = useApp(defaultAppData); const [postalCode, setPostalCode] = useState(false); const [cardSubmitted, setCardSubmitted] = useState(false); const [error, setError] = useState(null); @@ -30,9 +30,10 @@ export default ({ hasCard, computeProduct, newInstance, storageProduct, setPurch setError(payload.error); setProcessing(false); } else { - await addPaymentMethod({ payment_method_id: payload.paymentMethod.id, stripe_customer_id: lmsData.customer.stripe_customer_object.id, auth: lmsData.auth }); - const customer = await getCustomer({ auth: lmsData.auth }); - setLMSData({ ...lmsData, customer }); + console.log(lmsAuth); + await addPaymentMethod({ auth: lmsAuth, payload: { payment_method_id: payload.paymentMethod.id, stripe_customer_id: appData.customer.stripe_customer_object.id } }); + const customer = await getCustomer({ auth: lmsAuth }); + setAppData({ ...appData, customer }); setProcessing(false); setAddedCard(true); } @@ -43,7 +44,7 @@ export default ({ hasCard, computeProduct, newInstance, storageProduct, setPurch return ( -
Credit Card Details
+
Credit Card Details
{addedCard || hasCard ? ( <> @@ -158,7 +159,7 @@ export default ({ hasCard, computeProduct, newInstance, storageProduct, setPurch {error && ( -
+

{error.message}
diff --git a/src/components/login/index.js b/src/components/login/index.js index 3fc7e327b..b33aa4b21 100644 --- a/src/components/login/index.js +++ b/src/components/login/index.js @@ -3,42 +3,55 @@ import { Card, CardBody, Input, Button } from '@nio/ui-kit'; import useAsyncEffect from 'use-async-effect'; import { useHistory } from 'react-router'; -import useLMS from '../../state/stores/lmsData'; -import getUser from '../../api/lms/getUser'; -import getCustomer from '../../api/lms/getCustomer'; +import useLMS from '../../state/stores/lmsAuth'; + import defaultAuthFormData from '../../state/defaults/defaultAuthFormData'; -import defaultLMSData from '../../state/defaults/defaultLMSData'; +import defaultLMSAuth from '../../state/defaults/defaultLMSAuth'; + +import getUser from '../../api/lms/getUser'; +import useApp from '../../state/stores/appData'; +import defaultAppData from '../../state/defaults/defaultAppData'; +import isEmail from '../../util/isEmail'; export default () => { - const [lmsData, setLMSData] = useLMS(defaultLMSData); + const [lmsAuth, setLMSAuth] = useLMS(defaultLMSAuth); + const [appData, setAppData] = useApp(defaultAppData); + const [formState, setFormState] = useState({ submitted: false, error: false }); const [formData, updateForm] = useState(defaultAuthFormData); const history = useHistory(); - useAsyncEffect(async () => { - const { submitted, user, pass } = formData; + const handleKeydown = (e) => { if (e.keyCode === 13) setFormState({ submitted: true }); }; + useAsyncEffect(async () => { + const { user, pass } = formData; + const { submitted } = formState; if (submitted) { - if (user !== 'admin' && pass !== 'Abc1234!') { - updateForm({ ...formData, error: 'unknown user or password', submitted: false }); + if (!isEmail(user)) { + setFormState({ error: 'invalid email supplied', submitted: false }); + } else if (!user || !pass) { + setFormState({ error: 'all fields are required', submitted: false }); } else { - const currentUser = await getUser({ auth: { user: formData.user, pass: formData.pass } }); - if (currentUser.error) { - updateForm({ ...formData, error: currentUser.error, submitted: false }); - setLMSData(defaultLMSData); + const response = await getUser({ auth: { user, pass }, payload: { email: user } }); + if (response.result === false) { + setFormState({ error: response.message, submitted: false }); + setLMSAuth(defaultLMSAuth); } else { - const customer = await getCustomer({ auth: { user: formData.user, pass: formData.pass } }); - setLMSData({ ...lmsData, auth: { user: formData.user, pass: formData.pass }, currentUser, customer }); + setLMSAuth({ user, pass }); + setFormState({ error: false, submitted: false }); + setAppData({ ...appData, user: response }); history.push('/instances'); } } } - }, [formData]); + }, [formState]); useAsyncEffect(() => { - if (lmsData?.auth?.user && lmsData?.auth?.pass && !formData.submitted) { - updateForm({ user: lmsData.auth.user, pass: lmsData.auth.pass, submitted: true, error: false }); + const { user, pass } = lmsAuth; + const { submitted } = formData; + if (user && pass && !submitted) { + updateForm({ user, pass, submitted: true, error: false }); } - }, [lmsData]); + }, [lmsAuth]); return (
@@ -47,20 +60,22 @@ export default () => { updateForm({ ...formData, user: e.target.value })} + onKeyDown={handleKeydown} className="mb-2 text-center" type="text" - title="username" - placeholder="username" + title="email" + placeholder="email address" /> updateForm({ ...formData, pass: e.target.value })} + onKeyDown={handleKeydown} className="mb-4 text-center" type="password" title="password" placeholder="password" />
); diff --git a/src/components/navs/topnav.js b/src/components/navs/topnav.js index 7d8071c6b..32c538ea9 100644 --- a/src/components/navs/topnav.js +++ b/src/components/navs/topnav.js @@ -3,14 +3,17 @@ import { Navbar, Nav, NavItem, NavLink as DumbLink } from '@nio/ui-kit'; import { useHistory } from 'react-router'; import { NavLink } from 'react-router-dom'; -import useLMS from '../../state/stores/lmsData'; -import defaultLMSData from '../../state/defaults/defaultLMSData'; +import useLMS from '../../state/stores/lmsAuth'; +import defaultLMSAuth from '../../state/defaults/defaultLMSAuth'; +import useApp from '../../state/stores/appData'; +import defaultAppData from '../../state/defaults/defaultAppData'; export default () => { const history = useHistory(); - const [lmsData, setLMSData] = useLMS(defaultLMSData); + const [lmsAuth, setLMSAuth] = useLMS(defaultLMSAuth); + const [, setAppData] = useApp(defaultAppData); - return lmsData.auth && ( + return lmsAuth.user ? (