From ebc0f593e8888ce07baa854e81ee1feffef8f8fe Mon Sep 17 00:00:00 2001 From: bourquecharles Date: Sun, 25 Apr 2021 16:38:36 -0400 Subject: [PATCH] Initial push --- .env | 1 + package-lock.json | 450 ++++++++++++++++++-- package.json | 20 +- public/index.html | 2 +- src/App.css | 39 -- src/App.js | 95 ++--- src/app/store.js | 8 - src/assets/icons/index.js | 262 ++++++++++++ src/assets/icons/logo-negative.js | 30 ++ src/assets/icons/logo.js | 29 ++ src/assets/icons/sygnet.js | 9 + src/components/DeviceActions.js | 35 ++ src/components/DeviceConfiguration.js | 207 +++++++++ src/components/DeviceHealth.js | 59 +++ src/components/DeviceList.js | 220 ++++++++++ src/containers/TheContent.js | 45 ++ src/containers/TheFooter.js | 19 + src/containers/TheHeader.js | 78 ++++ src/containers/TheLayout.js | 33 ++ src/containers/TheSidebar.js | 58 +++ src/containers/_nav.js | 22 + src/containers/index.js | 13 + src/features/counter/Counter.js | 67 --- src/features/counter/Counter.module.css | 78 ---- src/features/counter/counterAPI.js | 6 - src/features/counter/counterSlice.js | 73 ---- src/features/counter/counterSlice.spec.js | 33 -- src/index.js | 16 +- src/logo.svg | 1 - src/routes.js | 13 + src/scss/_custom.scss | 1 + src/scss/_fixes.scss | 16 + src/scss/_variables.scss | 1 + src/scss/style.scss | 11 + src/store.js | 20 + src/utils/authHelper.js | 13 + src/utils/axiosInstance.js | 41 ++ src/utils/helper.js | 3 + src/views/icons/brands/Brands.js | 36 ++ src/views/icons/coreui-icons/CoreUIIcons.js | 23 + src/views/icons/flags/Flags.js | 23 + src/views/icons/index.js | 7 + src/views/pages/DeviceListPage.js | 12 + src/views/pages/DevicePage.js | 124 ++++++ src/views/pages/Login.js | 92 ++++ 45 files changed, 2046 insertions(+), 398 deletions(-) create mode 100644 .env delete mode 100644 src/App.css delete mode 100644 src/app/store.js create mode 100644 src/assets/icons/index.js create mode 100644 src/assets/icons/logo-negative.js create mode 100644 src/assets/icons/logo.js create mode 100644 src/assets/icons/sygnet.js create mode 100644 src/components/DeviceActions.js create mode 100644 src/components/DeviceConfiguration.js create mode 100644 src/components/DeviceHealth.js create mode 100644 src/components/DeviceList.js create mode 100644 src/containers/TheContent.js create mode 100644 src/containers/TheFooter.js create mode 100644 src/containers/TheHeader.js create mode 100644 src/containers/TheLayout.js create mode 100644 src/containers/TheSidebar.js create mode 100644 src/containers/_nav.js create mode 100644 src/containers/index.js delete mode 100644 src/features/counter/Counter.js delete mode 100644 src/features/counter/Counter.module.css delete mode 100644 src/features/counter/counterAPI.js delete mode 100644 src/features/counter/counterSlice.js delete mode 100644 src/features/counter/counterSlice.spec.js delete mode 100644 src/logo.svg create mode 100644 src/routes.js create mode 100644 src/scss/_custom.scss create mode 100644 src/scss/_fixes.scss create mode 100644 src/scss/_variables.scss create mode 100644 src/scss/style.scss create mode 100644 src/store.js create mode 100644 src/utils/authHelper.js create mode 100644 src/utils/axiosInstance.js create mode 100644 src/utils/helper.js create mode 100644 src/views/icons/brands/Brands.js create mode 100644 src/views/icons/coreui-icons/CoreUIIcons.js create mode 100644 src/views/icons/flags/Flags.js create mode 100644 src/views/icons/index.js create mode 100644 src/views/pages/DeviceListPage.js create mode 100644 src/views/pages/DevicePage.js create mode 100644 src/views/pages/Login.js diff --git a/.env b/.env new file mode 100644 index 0000000..285d4a2 --- /dev/null +++ b/.env @@ -0,0 +1 @@ +REACT_APP_BASE_URL=https://localhost:16001/api/v1 \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index a7261b4..618043f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1152,6 +1152,76 @@ "minimist": "^1.2.0" } }, + "@coreui/chartjs": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@coreui/chartjs/-/chartjs-2.0.0.tgz", + "integrity": "sha512-degpSo1MqSWomkNwuXk2VQijEENqkaufEGI/i6/3ClVQNZQIWB5NG6QWA/aCTXt9Y/3tVfnuTzDC4YHw7E+Brg==", + "requires": { + "@coreui/coreui": "^3.0.0-beta.1", + "chart.js": "^2.8.0" + } + }, + "@coreui/coreui": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/@coreui/coreui/-/coreui-3.4.0.tgz", + "integrity": "sha512-WqzockdWVkXUNmNwlqdu+AxM+9JoiWGe4rKaySu/dZme1NvVOn2ukjJlpTkssal8UKcSHyitzNixtkMCmUxE1A==" + }, + "@coreui/icons": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@coreui/icons/-/icons-2.0.1.tgz", + "integrity": "sha512-gBfFRLPUt3Bv9EZbJRbT3sQRHrhH0c4dRbeE9GpWJgJY8kvE9+3Hf5xGK9XyQhFynHx4o2WQeMxsReQLddlK9w==" + }, + "@coreui/icons-react": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@coreui/icons-react/-/icons-react-1.1.0.tgz", + "integrity": "sha512-OXDg09RsxlK5t6WizudsJUxgzJSAHeytwVG1hqn2ww5zIrJn5++5rNTp95N/kff4/er4f7jspwJ1/7n6mQAz2Q==", + "requires": { + "classnames": "^2.2.6", + "prop-types": "^15.7.2" + } + }, + "@coreui/react": { + "version": "3.4.6", + "resolved": "https://registry.npmjs.org/@coreui/react/-/react-3.4.6.tgz", + "integrity": "sha512-qu9/2kDNb24jXMaoGolOM5Jp9+wdweVWPJFixnAOMCobhUt0TNHa9yJakCfr7mopMV8teYvWBjBhl5I2er1/xw==", + "requires": { + "@babel/runtime": "^7.13.10", + "@coreui/icons": "^2.0.0-rc.0", + "@coreui/icons-react": "^1.1.0", + "@coreui/utils": "~1.3.1", + "@popperjs/core": "^2.9.1", + "classnames": "~2.2.6", + "core-js": "^3.9.1", + "perfect-scrollbar": "~1.5.0", + "prop-types": "~15.7.2", + "react-transition-group": "~4.4.1", + "tippy.js": "^6.3.1" + }, + "dependencies": { + "classnames": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.6.tgz", + "integrity": "sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==" + } + } + }, + "@coreui/react-chartjs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@coreui/react-chartjs/-/react-chartjs-1.1.0.tgz", + "integrity": "sha512-xa925PmaBeh+2x+AY/macovW7KOe4W+VaxNcLKndY10GFSsEjryCrY7s9QXiIUqoQJQMXMeiXMbSoTuhIJ/aEA==", + "requires": { + "@coreui/chartjs": "^2.0.0", + "@types/chart.js": "^2.9.31", + "chart.js": "^2.9.4", + "classnames": "^2.2.6", + "prop-types": "^15.7.2" + } + }, + "@coreui/utils": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@coreui/utils/-/utils-1.3.1.tgz", + "integrity": "sha512-WuWHX7bg89cJH34TWVsLe9RsxzBhTApj+X2Ja19xhjcpxt5Gv11Ozm+fwYt6DD7DgncTvpwYrMcnNlpp701UOg==" + }, "@csstools/convert-colors": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@csstools/convert-colors/-/convert-colors-1.4.0.tgz", @@ -1193,6 +1263,35 @@ } } }, + "@fortawesome/fontawesome-common-types": { + "version": "0.2.35", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.35.tgz", + "integrity": "sha512-IHUfxSEDS9dDGqYwIW7wTN6tn/O8E0n5PcAHz9cAaBoZw6UpG20IG/YM3NNLaGPwPqgjBAFjIURzqoQs3rrtuw==" + }, + "@fortawesome/fontawesome-svg-core": { + "version": "1.2.35", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.35.tgz", + "integrity": "sha512-uLEXifXIL7hnh2sNZQrIJWNol7cTVIzwI+4qcBIq9QWaZqUblm0IDrtSqbNg+3SQf8SMGHkiSigD++rHmCHjBg==", + "requires": { + "@fortawesome/fontawesome-common-types": "^0.2.35" + } + }, + "@fortawesome/free-solid-svg-icons": { + "version": "5.15.3", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.15.3.tgz", + "integrity": "sha512-XPeeu1IlGYqz4VWGRAT5ukNMd4VHUEEJ7ysZ7pSSgaEtNvSo+FLurybGJVmiqkQdK50OkSja2bfZXOeyMGRD8Q==", + "requires": { + "@fortawesome/fontawesome-common-types": "^0.2.35" + } + }, + "@fortawesome/react-fontawesome": { + "version": "0.1.14", + "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.1.14.tgz", + "integrity": "sha512-4wqNb0gRLVaBm/h+lGe8UfPPivcbuJ6ecI4hIgW0LjI7kzpYB9FkN0L9apbVzg+lsBdcTf0AlBtODjcSX5mmKA==", + "requires": { + "prop-types": "^15.7.2" + } + }, "@hapi/address": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.1.4.tgz", @@ -1824,15 +1923,23 @@ } } }, - "@reduxjs/toolkit": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.5.1.tgz", - "integrity": "sha512-PngZKuwVZsd+mimnmhiOQzoD0FiMjqVks6ituO1//Ft5UEX5Ca9of13NEjo//pU22Jk7z/mdXVsmDfgsig1osA==", + "@popperjs/core": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.9.2.tgz", + "integrity": "sha512-VZMYa7+fXHdwIq1TDhSXoVmSPEGM/aa+6Aiq3nVVJ9bXr24zScr+NlKFKC3iPljA7ho/GAZr+d2jOf5GIRC30Q==" + }, + "@restart/context": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@restart/context/-/context-2.1.4.tgz", + "integrity": "sha512-INJYZQJP7g+IoDUh/475NlGiTeMfwTXUEr3tmRneckHIxNolGOW9CTq83S8cxq0CgJwwcMzMJFchxvlwe7Rk8Q==" + }, + "@restart/hooks": { + "version": "0.3.26", + "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.3.26.tgz", + "integrity": "sha512-7Hwk2ZMYm+JLWcb7R9qIXk1OoUg1Z+saKWqZXlrvFwT3w6UArVNWgxYOzf+PJoK9zZejp8okPAKTctthhXLt5g==", "requires": { - "immer": "^8.0.1", - "redux": "^4.0.0", - "redux-thunk": "^2.3.0", - "reselect": "^4.0.0" + "lodash": "^4.17.20", + "lodash-es": "^4.17.20" } }, "@rollup/plugin-node-resolve": { @@ -2252,6 +2359,19 @@ "@babel/types": "^7.3.0" } }, + "@types/chart.js": { + "version": "2.9.32", + "resolved": "https://registry.npmjs.org/@types/chart.js/-/chart.js-2.9.32.tgz", + "integrity": "sha512-d45JiRQwEOlZiKwukjqmqpbqbYzUX2yrXdH9qVn6kXpPDsTYCo6YbfFOlnUaJ8S/DhJwbBJiLsMjKpW5oP8B2A==", + "requires": { + "moment": "^2.10.2" + } + }, + "@types/classnames": { + "version": "2.2.11", + "resolved": "https://registry.npmjs.org/@types/classnames/-/classnames-2.2.11.tgz", + "integrity": "sha512-2koNhpWm3DgWRp5tpkiJ8JGc1xTn2q0l+jUNUE7oMKXUf5NpI9AIdC4kbjGNFBdHtcxBD18LAksoudAVhFKCjw==" + }, "@types/eslint": { "version": "7.2.8", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.2.8.tgz", @@ -2297,6 +2417,11 @@ "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz", "integrity": "sha512-giAlZwstKbmvMk1OO7WXSj4OZ0keXAcl2TQq4LWHiiPH2ByaH7WeUzng+Qej8UPxxv+8lRTuouo0iaNDBuzIBA==" }, + "@types/invariant": { + "version": "2.2.34", + "resolved": "https://registry.npmjs.org/@types/invariant/-/invariant-2.2.34.tgz", + "integrity": "sha512-lYUtmJ9BqUN688fGY1U1HZoWT1/Jrmgigx2loq4ZcJpICECm/Om3V314BxdzypO0u5PORKGMM6x0OXaljV1YFg==" + }, "@types/istanbul-lib-coverage": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", @@ -2392,6 +2517,14 @@ "redux": "^4.0.0" } }, + "@types/react-transition-group": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.1.tgz", + "integrity": "sha512-vIo69qKKcYoJ8wKCJjwSgCTM+z3chw3g18dkrDfVX665tMH7tmbDxEAnPdey4gTlwZz5QuHGzd+hul0OVZDqqQ==", + "requires": { + "@types/react": "*" + } + }, "@types/resolve": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz", @@ -2574,6 +2707,11 @@ } } }, + "@types/warning": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.0.tgz", + "integrity": "sha1-DSUBJorY+ZYrdA04fEZU9fjiPlI=" + }, "@types/webpack": { "version": "4.41.27", "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.27.tgz", @@ -3238,6 +3376,22 @@ "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.1.4.tgz", "integrity": "sha512-Pdgfv6iP0gNx9ejRGa3zE7Xgkj/iclXqLfe7BnatdZz0QnLZ3jrRHUVH8wNSdN68w05Sk3ShGTb3ydktMTooig==" }, + "axios": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", + "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==", + "requires": { + "follow-redirects": "^1.10.0" + } + }, + "axios-retry": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/axios-retry/-/axios-retry-3.1.9.tgz", + "integrity": "sha512-NFCoNIHq8lYkJa6ku4m+V1837TP6lCa7n79Iuf8/AqATAHYB0ISaAS1eyIenDOfHOLtym34W65Sjke2xjg2fsA==", + "requires": { + "is-retry-allowed": "^1.1.0" + } + }, "axobject-query": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz", @@ -3791,8 +3945,7 @@ "binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "optional": true + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==" }, "bindings": { "version": "1.5.0", @@ -3873,6 +4026,11 @@ "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" }, + "bootstrap": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.0.tgz", + "integrity": "sha512-Io55IuQY3kydzHtbGvQya3H+KorS/M9rSNyfCGCg9WZ4pyT/lCxIlpJgG1GXW/PswzC84Tr2fBYi+7+jFVQQBw==" + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -4188,6 +4346,32 @@ "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==" }, + "chart.js": { + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.9.4.tgz", + "integrity": "sha512-B07aAzxcrikjAPyV+01j7BmOpxtQETxTSlQ26BEYJ+3iUkbNKaOJ/nDbT6JjyqYxseM0ON12COHYdU2cTIjC7A==", + "requires": { + "chartjs-color": "^2.1.0", + "moment": "^2.10.2" + } + }, + "chartjs-color": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chartjs-color/-/chartjs-color-2.4.1.tgz", + "integrity": "sha512-haqOg1+Yebys/Ts/9bLo/BqUcONQOdr/hoEr2LLTRl6C5LXctUdHxsCYfvQVg5JIxITrfCNUDr4ntqmQk9+/0w==", + "requires": { + "chartjs-color-string": "^0.6.0", + "color-convert": "^1.9.3" + } + }, + "chartjs-color-string": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/chartjs-color-string/-/chartjs-color-string-0.6.0.tgz", + "integrity": "sha512-TIB5OKn1hPJvO7JcteW4WY/63v6KwEdt6udfnDE9iCAZgy+V4SrbSxoIbTw/xkUIapjEI4ExGtD0+6D3KyFd7A==", + "requires": { + "color-name": "^1.0.0" + } + }, "check-types": { "version": "11.1.2", "resolved": "https://registry.npmjs.org/check-types/-/check-types-11.1.2.tgz", @@ -4197,7 +4381,6 @@ "version": "3.5.1", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", - "optional": true, "requires": { "anymatch": "~3.1.1", "braces": "~3.0.2", @@ -4259,6 +4442,11 @@ } } }, + "classnames": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz", + "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==" + }, "clean-css": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.3.tgz", @@ -5312,6 +5500,15 @@ "utila": "~0.4" } }, + "dom-helpers": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.0.tgz", + "integrity": "sha512-Ru5o9+V8CpunKnz5LGgWXkmrH/20cGKwcHwS4m73zIvs54CN9epEmT/HLqFJW3kXpakAFkEdzgy1hzlJe3E4OQ==", + "requires": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, "dom-serializer": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", @@ -7328,6 +7525,19 @@ "resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz", "integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==" }, + "history": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", + "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", + "requires": { + "@babel/runtime": "^7.1.2", + "loose-envify": "^1.2.0", + "resolve-pathname": "^3.0.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0", + "value-equal": "^1.0.1" + } + }, "hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", @@ -7818,6 +8028,14 @@ "side-channel": "^1.0.4" } }, + "invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "requires": { + "loose-envify": "^1.0.0" + } + }, "ip": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", @@ -7878,7 +8096,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "optional": true, "requires": { "binary-extensions": "^2.0.0" } @@ -8091,6 +8308,11 @@ "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==" }, + "is-retry-allowed": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz", + "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==" + }, "is-root": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-root/-/is-root-2.1.0.tgz", @@ -9944,6 +10166,11 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" + }, "lodash._reinterpolate": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", @@ -10208,6 +10435,15 @@ "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==" }, + "mini-create-react-context": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.1.tgz", + "integrity": "sha512-YWCYEmd5CQeHGSAKrYvXgmzzkrvssZcuuQDDeqkT+PziKGMgE+0MCCtcKbROzocGBG1meBLl2FotlRwf4gAzbQ==", + "requires": { + "@babel/runtime": "^7.12.1", + "tiny-warning": "^1.0.3" + } + }, "mini-css-extract-plugin": { "version": "0.11.3", "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.11.3.tgz", @@ -10357,6 +10593,11 @@ "minimist": "^1.2.5" } }, + "moment": { + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", + "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==" + }, "move-concurrently": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", @@ -11089,6 +11330,11 @@ "sha.js": "^2.4.8" } }, + "perfect-scrollbar": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/perfect-scrollbar/-/perfect-scrollbar-1.5.0.tgz", + "integrity": "sha512-NrNHJn5mUGupSiheBTy6x+6SXCFbLlm8fVZh9moIzw/LgqElN5q4ncR4pbCBCYuCJ8Kcl9mYM0NgDxvW+b4LxA==" + }, "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", @@ -12344,6 +12590,15 @@ "react-is": "^16.8.1" } }, + "prop-types-extra": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/prop-types-extra/-/prop-types-extra-1.1.1.tgz", + "integrity": "sha512-59+AHNnHYCdiC+vMwY52WmvP5dM3QLeoumYuEyceQDi9aEhtwN9zIQ2ZNo25sMyXnbh32h+P1ezDsUpUH3JAew==", + "requires": { + "react-is": "^16.3.2", + "warning": "^4.0.0" + } + }, "proxy-addr": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", @@ -12527,6 +12782,31 @@ "whatwg-fetch": "^3.4.1" } }, + "react-bootstrap": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-1.5.2.tgz", + "integrity": "sha512-mGKPY5+lLd7Vtkx2VFivoRkPT4xAHazuFfIhJLTEgHlDfIUSePn7qrmpZe5gXH9zvHV0RsBaQ9cLfXjxnZrOpA==", + "requires": { + "@babel/runtime": "^7.13.8", + "@restart/context": "^2.1.4", + "@restart/hooks": "^0.3.26", + "@types/classnames": "^2.2.10", + "@types/invariant": "^2.2.33", + "@types/prop-types": "^15.7.3", + "@types/react": ">=16.9.35", + "@types/react-transition-group": "^4.4.1", + "@types/warning": "^3.0.0", + "classnames": "^2.2.6", + "dom-helpers": "^5.1.2", + "invariant": "^2.2.4", + "prop-types": "^15.7.2", + "prop-types-extra": "^1.1.0", + "react-overlays": "^5.0.0", + "react-transition-group": "^4.4.1", + "uncontrollable": "^7.2.1", + "warning": "^4.0.3" + } + }, "react-dev-utils": { "version": "11.0.4", "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-11.0.4.tgz", @@ -12653,6 +12933,26 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" + }, + "react-overlays": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/react-overlays/-/react-overlays-5.0.0.tgz", + "integrity": "sha512-TKbqfAv23TFtCJ2lzISdx76p97G/DP8Rp4TOFdqM9n8GTruVYgE3jX7Zgb8+w7YJ18slTVcDTQ1/tFzdCqjVhA==", + "requires": { + "@babel/runtime": "^7.12.1", + "@popperjs/core": "^2.5.3", + "@restart/hooks": "^0.3.25", + "@types/warning": "^3.0.0", + "dom-helpers": "^5.2.0", + "prop-types": "^15.7.2", + "uncontrollable": "^7.0.0", + "warning": "^4.0.3" + } + }, "react-redux": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.3.tgz", @@ -12671,6 +12971,52 @@ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.8.3.tgz", "integrity": "sha512-X8jZHc7nCMjaCqoU+V2I0cOhNW+QMBwSUkeXnTi8IPe6zaRWfn60ZzvFDZqWPfmSJfjub7dDW1SP0jaHWLu/hg==" }, + "react-router": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.0.tgz", + "integrity": "sha512-smz1DUuFHRKdcJC0jobGo8cVbhO3x50tCL4icacOlcwDOEQPq4TMqwx3sY1TP+DvtTgz4nm3thuo7A+BK2U0Dw==", + "requires": { + "@babel/runtime": "^7.1.2", + "history": "^4.9.0", + "hoist-non-react-statics": "^3.1.0", + "loose-envify": "^1.3.1", + "mini-create-react-context": "^0.4.0", + "path-to-regexp": "^1.7.0", + "prop-types": "^15.6.2", + "react-is": "^16.6.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "requires": { + "isarray": "0.0.1" + } + } + } + }, + "react-router-dom": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.2.0.tgz", + "integrity": "sha512-gxAmfylo2QUjcwxI63RhQ5G85Qqt4voZpUXSEqCwykV0baaOTQDR1f0PmY8AELqIyVc0NEZUj0Gov5lNGcXgsA==", + "requires": { + "@babel/runtime": "^7.1.2", + "history": "^4.9.0", + "loose-envify": "^1.3.1", + "prop-types": "^15.6.2", + "react-router": "5.2.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" + } + }, "react-scripts": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-4.0.3.tgz", @@ -12737,6 +13083,17 @@ "workbox-webpack-plugin": "5.1.4" } }, + "react-transition-group": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.1.tgz", + "integrity": "sha512-Djqr7OQ2aPUiYurhPalTrVy9ddmFCCzwhqQmtN+J3+3DzLO209Fdr70QrN8Z3DsglWql6iY1lDWAfpFiBtuKGw==", + "requires": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + } + }, "read-pkg": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", @@ -12830,7 +13187,6 @@ "version": "3.5.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", - "optional": true, "requires": { "picomatch": "^2.2.1" } @@ -12861,11 +13217,6 @@ "symbol-observable": "^1.2.0" } }, - "redux-thunk": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.3.0.tgz", - "integrity": "sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw==" - }, "regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -13092,11 +13443,6 @@ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" }, - "reselect": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.0.0.tgz", - "integrity": "sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA==" - }, "resolve": { "version": "1.18.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.18.1.tgz", @@ -13126,6 +13472,11 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" }, + "resolve-pathname": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", + "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==" + }, "resolve-url": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", @@ -13511,6 +13862,14 @@ "resolved": "https://registry.npmjs.org/sanitize.css/-/sanitize.css-10.0.0.tgz", "integrity": "sha512-vTxrZz4dX5W86M6oVWVdOVe72ZiPs41Oi7Z6Km4W5Turyz28mrXSJhhEBZoRtzJWIv3833WKVwLSDWWkEfupMg==" }, + "sass": { + "version": "1.32.8", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.32.8.tgz", + "integrity": "sha512-Sl6mIeGpzjIUZqvKnKETfMf0iDAswD9TNlv13A7aAF3XZlRPMq4VvJWBC2N2DXbp94MQVdNSFG6LfF/iOXrPHQ==", + "requires": { + "chokidar": ">=2.0.0 <4.0.0" + } + }, "sass-loader": { "version": "10.1.1", "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-10.1.1.tgz", @@ -14784,6 +15143,24 @@ "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=" }, + "tiny-invariant": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.1.0.tgz", + "integrity": "sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==" + }, + "tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" + }, + "tippy.js": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-6.3.1.tgz", + "integrity": "sha512-JnFncCq+rF1dTURupoJ4yPie5Cof978inW6/4S6kmWV7LL9YOSEVMifED3KdrVPEG+Z/TFH2CDNJcQEfaeuQww==", + "requires": { + "@popperjs/core": "^2.8.3" + } + }, "tmpl": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz", @@ -14984,6 +15361,17 @@ "which-boxed-primitive": "^1.0.2" } }, + "uncontrollable": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-7.2.1.tgz", + "integrity": "sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ==", + "requires": { + "@babel/runtime": "^7.6.3", + "@types/react": ">=16.9.11", + "invariant": "^2.2.4", + "react-lifecycles-compat": "^3.0.4" + } + }, "unicode-canonical-property-names-ecmascript": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", @@ -15223,8 +15611,7 @@ "uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "optional": true + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" }, "v8-compile-cache": { "version": "2.3.0", @@ -15257,6 +15644,11 @@ "spdx-expression-parse": "^3.0.0" } }, + "value-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", + "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==" + }, "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -15311,6 +15703,14 @@ "makeerror": "1.0.x" } }, + "warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "requires": { + "loose-envify": "^1.0.0" + } + }, "watchpack": { "version": "1.7.5", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.5.tgz", diff --git a/package.json b/package.json index 79d0228..4e95fb2 100644 --- a/package.json +++ b/package.json @@ -3,14 +3,30 @@ "version": "0.1.0", "private": true, "dependencies": { - "@reduxjs/toolkit": "^1.5.1", + "@coreui/chartjs": "^2.0.0", + "@coreui/coreui": "^3.4.0", + "@coreui/icons": "^2.0.1", + "@coreui/icons-react": "^1.1.0", + "@coreui/react": "^3.4.6", + "@coreui/react-chartjs": "^1.1.0", + "@coreui/utils": "^1.3.1", + "@fortawesome/fontawesome-svg-core": "^1.2.35", + "@fortawesome/free-solid-svg-icons": "^5.15.3", + "@fortawesome/react-fontawesome": "^0.1.14", "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.5.0", "@testing-library/user-event": "^7.2.1", + "axios": "^0.21.1", + "axios-retry": "^3.1.9", + "bootstrap": "^4.6.0", "react": "^17.0.2", + "react-bootstrap": "^1.5.2", "react-dom": "^17.0.2", "react-redux": "^7.2.3", - "react-scripts": "4.0.3" + "react-router-dom": "^5.2.0", + "react-scripts": "4.0.3", + "sass": "^1.32.8", + "uuid": "^8.3.2" }, "scripts": { "start": "react-scripts start", diff --git a/public/index.html b/public/index.html index 1dbdca6..624932d 100644 --- a/public/index.html +++ b/public/index.html @@ -24,7 +24,7 @@ work correctly both with client-side routing and a non-root public URL. Learn how to configure a non-root public URL by running `npm run build`. --> - React Redux App + UcentralGW diff --git a/src/App.css b/src/App.css deleted file mode 100644 index 01cc586..0000000 --- a/src/App.css +++ /dev/null @@ -1,39 +0,0 @@ -.App { - text-align: center; -} - -.App-logo { - height: 40vmin; - pointer-events: none; -} - -@media (prefers-reduced-motion: no-preference) { - .App-logo { - animation: App-logo-float infinite 3s ease-in-out; - } -} - -.App-header { - min-height: 100vh; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - font-size: calc(10px + 2vmin); -} - -.App-link { - color: rgb(112, 76, 182); -} - -@keyframes App-logo-float { - 0% { - transform: translateY(0); - } - 50% { - transform: translateY(10px); - } - 100% { - transform: translateY(0px); - } -} diff --git a/src/App.js b/src/App.js index a40f66d..7f0910b 100644 --- a/src/App.js +++ b/src/App.js @@ -1,58 +1,41 @@ -import React from 'react'; -import logo from './logo.svg'; -import { Counter } from './features/counter/Counter'; -import './App.css'; +import React, { useEffect } from 'react'; +import { HashRouter, Route, Switch } from 'react-router-dom'; +import './scss/style.scss'; +import { useSelector, useDispatch } from 'react-redux'; -function App() { - return ( -
-
- logo - -

- Edit src/App.js and save to reload. -

- - Learn - - React - - , - - Redux - - , - - Redux Toolkit - - , and - - React Redux - - -
-
- ); -} +const loading = ( +
+
+
+) -export default App; +const TheLayout = React.lazy(() => import('./containers/TheLayout')); +const Login = React.lazy(() => import('./views/pages/Login')); +const Page404 = React.lazy(() => import('./views/pages/Page404')); +const Page500 = React.lazy(() => import('./views/pages/Page500')); + +const App = () => { + const isLoggedIn = useSelector(state => state.connected); + const dispatch = useDispatch(); + + useEffect(() => { + const token = sessionStorage.getItem('access_token'); + if (token !== undefined && token !== null) { + dispatch({type: 'set', connected: true}); + } + }, [dispatch]); + + return ( + + + + } /> + } /> + isLoggedIn ? : } /> + + + + ); +}; + +export default App; \ No newline at end of file diff --git a/src/app/store.js b/src/app/store.js deleted file mode 100644 index 9eca6d2..0000000 --- a/src/app/store.js +++ /dev/null @@ -1,8 +0,0 @@ -import { configureStore } from '@reduxjs/toolkit'; -import counterReducer from '../features/counter/counterSlice'; - -export const store = configureStore({ - reducer: { - counter: counterReducer, - }, -}); diff --git a/src/assets/icons/index.js b/src/assets/icons/index.js new file mode 100644 index 0000000..3cf7072 --- /dev/null +++ b/src/assets/icons/index.js @@ -0,0 +1,262 @@ +import { sygnet } from './sygnet' +import { logo } from './logo' +import { logoNegative } from './logo-negative' + +import { + cibSkype, + cibFacebook, + cibTwitter, + cibLinkedin, + cibFlickr, + cibTumblr, + cibXing, + cibGithub, + cibStackoverflow, + cibYoutube, + cibDribbble, + cibInstagram, + cibPinterest, + cibVk, + cibYahoo, + cibBehance, + cibReddit, + cibVimeo, + cibCcMastercard, + cibCcVisa, + cibStripe, + cibPaypal, + cibGooglePay, + cibCcAmex +} from '@coreui/icons' +import { + cifUs, + cifBr, + cifIn, + cifFr, + cifEs, + cifPl +} from '@coreui/icons' +import { + cilAlignCenter, + cilAlignLeft, + cilAlignRight, + cilApplicationsSettings, + cilArrowRight, + cilArrowTop, + cilAsterisk, + cilBan, + cilBasket, + cilBell, + cilBold, + cilBookmark, + cilCalculator, + cilCalendar, + cilCloudDownload, + cilChartPie, + cilCheck, + cilChevronBottom, + cilChevronLeft, + cilChevronRight, + cilChevronTop, + cilCircle, + cilCheckCircle, + cilCode, + cilCommentSquare, + cilCreditCard, + cilCursor, + cilCursorMove, + cilDrop, + cilDollar, + cilEnvelopeClosed, + cilEnvelopeLetter, + cilEnvelopeOpen, + cilEuro, + cilGlobeAlt, + cilGrid, + cilFile, + cilFullscreen, + cilFullscreenExit, + cilGraph, + cilHome, + cilInbox, + cilIndentDecrease, + cilIndentIncrease, + cilInputPower, + cilItalic, + cilJustifyCenter, + cilJustifyLeft, + cilLaptop, + cilLayers, + cilLightbulb, + cilList, + cilListNumbered, + cilListRich, + cilLocationPin, + cilLockLocked, + cilMagnifyingGlass, + cilMap, + cilMoon, + cilNotes, + cilOptions, + cilPaperclip, + cilPaperPlane, + cilPencil, + cilPeople, + cilPhone, + cilPrint, + cilPuzzle, + cilSave, + cilScrubber, + cilSettings, + cilShare, + cilShareAll, + cilShareBoxed, + cilShieldAlt, + cilSpeech, + cilSpeedometer, + cilSpreadsheet, + cilStar, + cilSun, + cilTags, + cilTask, + cilTrash, + cilUnderline, + cilUser, + cilUserFemale, + cilUserFollow, + cilUserUnfollow, + cilX, + cilXCircle, + cilWarning +} from '@coreui/icons' + +export const icons = Object.assign({}, { + sygnet, + logo, + logoNegative +}, { + cilAlignCenter, + cilAlignLeft, + cilAlignRight, + cilApplicationsSettings, + cilArrowRight, + cilArrowTop, + cilAsterisk, + cilBan, + cilBasket, + cilBell, + cilBold, + cilBookmark, + cilCalculator, + cilCalendar, + cilCloudDownload, + + cilChartPie, + cilCheck, + cilChevronBottom, + cilChevronLeft, + cilChevronRight, + cilChevronTop, + cilCircle, + cilCheckCircle, + cilCode, + cilCommentSquare, + cilCreditCard, + cilCursor, + cilCursorMove, + cilDrop, + cilDollar, + cilEnvelopeClosed, + cilEnvelopeLetter, + cilEnvelopeOpen, + cilEuro, + cilGlobeAlt, + cilGrid, + cilFile, + cilFullscreen, + cilFullscreenExit, + cilGraph, + cilHome, + cilInbox, + cilIndentDecrease, + cilIndentIncrease, + cilInputPower, + cilItalic, + cilJustifyCenter, + cilJustifyLeft, + cilLaptop, + cilLayers, + cilLightbulb, + cilList, + cilListNumbered, + cilListRich, + cilLocationPin, + cilLockLocked, + cilMagnifyingGlass, + cilMap, + cilMoon, + cilNotes, + cilOptions, + cilPaperclip, + cilPaperPlane, + cilPencil, + cilPeople, + cilPhone, + cilPrint, + cilPuzzle, + cilSave, + cilScrubber, + cilSettings, + cilShare, + cilShareAll, + cilShareBoxed, + cilShieldAlt, + cilSpeech, + cilSpeedometer, + cilSpreadsheet, + cilStar, + cilSun, + cilTags, + cilTask, + cilTrash, + cilUnderline, + cilUser, + cilUserFemale, + cilUserFollow, + cilUserUnfollow, + cilX, + cilXCircle, + cilWarning +}, { + cifUs, + cifBr, + cifIn, + cifFr, + cifEs, + cifPl +}, { + cibSkype, + cibFacebook, + cibTwitter, + cibLinkedin, + cibFlickr, + cibTumblr, + cibXing, + cibGithub, + cibStackoverflow, + cibYoutube, + cibDribbble, + cibInstagram, + cibPinterest, + cibVk, + cibYahoo, + cibBehance, + cibReddit, + cibVimeo, + cibCcMastercard, + cibCcVisa, + cibStripe, + cibPaypal, + cibGooglePay, + cibCcAmex +}) diff --git a/src/assets/icons/logo-negative.js b/src/assets/icons/logo-negative.js new file mode 100644 index 0000000..337875e --- /dev/null +++ b/src/assets/icons/logo-negative.js @@ -0,0 +1,30 @@ +export const logoNegative = ['608 134', ` + coreui react pro logo + + + + + + + + + + + + + + + + + + + + + + + + + + + +`] diff --git a/src/assets/icons/logo.js b/src/assets/icons/logo.js new file mode 100644 index 0000000..01d6ddd --- /dev/null +++ b/src/assets/icons/logo.js @@ -0,0 +1,29 @@ +export const logo = ['608 134', ` + coreui react pro + + + + + + + + + + + + + + + + + + + + + + + + + + +`] diff --git a/src/assets/icons/sygnet.js b/src/assets/icons/sygnet.js new file mode 100644 index 0000000..fddab7e --- /dev/null +++ b/src/assets/icons/sygnet.js @@ -0,0 +1,9 @@ +export const sygnet = ['160 160', ` + coreui logo + + + + + + +`] diff --git a/src/components/DeviceActions.js b/src/components/DeviceActions.js new file mode 100644 index 0000000..14f8a65 --- /dev/null +++ b/src/components/DeviceActions.js @@ -0,0 +1,35 @@ +import React from 'react' +import { + CButton, + CCard, + CCardHeader, + CCardBody, + CRow, + CCol + } from '@coreui/react' +import { useSelector } from 'react-redux'; + +const DeviceActions = () => { + let selectedDevice = useSelector(state => state.selectedDevice); + console.log(selectedDevice); + return ( + + + Device Actions + + + + + Reboot + + + Blink + + + + + + ); +} + +export default DeviceActions \ No newline at end of file diff --git a/src/components/DeviceConfiguration.js b/src/components/DeviceConfiguration.js new file mode 100644 index 0000000..bb0615d --- /dev/null +++ b/src/components/DeviceConfiguration.js @@ -0,0 +1,207 @@ +import React, { useState } from 'react' +import { + CCard, + CCardHeader, + CCardBody, + CFormGroup, + CCol, + CLabel, + CForm, + CInput, + CCollapse, + CCardFooter, + CButton +} from '@coreui/react' +import { useSelector } from 'react-redux'; +import { cleanTimestamp} from '../utils/helper'; + +const DeviceConfiguration = () => { + const [collapse, setCollapse] = useState(false) + let device = useSelector(state => state.selectedDevice); + + const toggle = (e) => { + setCollapse(!collapse); + e.preventDefault(); + } + + if(device){ + return ( + + + Device #{device.serialNumber} Configuration + + + + + + UUID : + + + {device.UUID } + + + + + Serial Number : + + + {device.serialNumber } + + + + + Device Type : + + + { device.deviceType } + + + + + Last Configuration Change : + + + {cleanTimestamp(device.lastConfigurationChange) } + + + + + MAC address : + + + { device.macAddress } + + + + + + Created : + + + { cleanTimestamp(device.createdTimestamp) } + + + + + Last Configuration Download : + + + {cleanTimestamp(device.lastConfigurationDownload) } + + + + + Manufacturer : + + + { device.manufacturer } + + + + + Notes : + + + + + + + + Owner : + + + { device.owner } + + + + + Location : + + + { device.location } + + + + + More details + + + + + ); + } + + return ( + + + Device Configuration + + + + + ); +} + + + +export default DeviceConfiguration + +/* + +location ???? + +configuration + log + _log_hostname + _log_ip + _log_port + _log_proto + _log_size + network (is a list) + cfg + proto + + OROROROR + + peeraddr + vid + + OROROROR + + dhcp + leasetime + limit + start + ip6assign + ipaddr + leases (is a list) + hostname + ip + mac + mtu + netmask + proto + + mode + vlan (sometimes) + + ntp + enable_server + enabled + server (list of strings) + + + + + + + + + + + +*/ \ No newline at end of file diff --git a/src/components/DeviceHealth.js b/src/components/DeviceHealth.js new file mode 100644 index 0000000..4ba57b2 --- /dev/null +++ b/src/components/DeviceHealth.js @@ -0,0 +1,59 @@ +import React from 'react' +import { + CWidgetProgressIcon, + CWidgetDropdown, + CDropdown, + CDropdownToggle, + CDropdownItem, + CDropdownMenu, + CCardFooter, + CLink, + CProgressBar +} from '@coreui/react' +import CIcon from '@coreui/icons-react' +import { cilHeart, cilSettings } from '@coreui/icons'; +import { useSelector } from 'react-redux'; + +const healthPopoverContent = "100%: Perfect 90%-99%: Good 0%-90%: Bad"; + +const DeviceHealth = () => { + let selectedDevice = useSelector(state => state.selectedDevice); + let sanityLevel; + let barColor; + + if(selectedDevice && selectedDevice.healthChecks && selectedDevice.healthChecks.length > 0){ + sanityLevel = selectedDevice.healthChecks[0].sanity; + + if(sanityLevel === 100) + barColor = "gradient-success"; + else if (sanityLevel >= 90) + barColor = "gradient-warning"; + else + barColor = "gradient-danger"; + + } + + return ( + + + + {/* Need inline styling because CDropdownToggle does not take into account the + parent's inverse value*/} + + + + + View Logs + + + + ); +} + +export default DeviceHealth \ No newline at end of file diff --git a/src/components/DeviceList.js b/src/components/DeviceList.js new file mode 100644 index 0000000..1d4587b --- /dev/null +++ b/src/components/DeviceList.js @@ -0,0 +1,220 @@ +import React, { useEffect, useState, useCallback } from 'react' +import { + CBadge, + CCardBody, + CDataTable, + CCollapse, + CButton, + CLink +} from '@coreui/react' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faSync } from '@fortawesome/free-solid-svg-icons' +import { getToken } from '../utils/authHelper'; +import axiosInstance from '../utils/axiosInstance'; + +const DeviceList = () => { + const [devices, setDevices] = useState([]); + const [details, setDetails] = useState([]); + const [loading, setLoading] = useState(true); + const [lastRefresh, setLastRefresh] = useState([]); + + //Loading the devices + const refreshDevices = useCallback(() => { + const token = getToken(); + + const headers = { + 'Accept': 'application/json', + 'Authorization': `Bearer ${token}` + }; + + axiosInstance.get('/devices', { + headers: headers + }) + .then((response) => { + const date = new Date(); + const dateAsString = date.toLocaleString(); + setLastRefresh(dateAsString); + addStatusToDeviceList(response.data.devices); + }) + .catch(error => { + setLoading(false); + console.log(error.response); + }); + }, []); + + //Getting the status for each device + const addStatusToDeviceList = (devices) => { + const token = getToken(); + + const headers = { + 'Accept': 'application/json', + 'Authorization': `Bearer ${token}` + }; + + for(let i = 0; i< devices.length; i++){ + axiosInstance.get(`/device/${devices[i].serialNumber}/status`, { + headers: headers + }) + .then((response) => { + //Merging the device object in the array with the one we received from the API + devices[i] = {...devices[i], ...response.data}; + devices[i].ipAddress = devices[i].ipAddress.substr(0, devices[i].ipAddress.indexOf(':')); + setDevices(devices); + }) + .catch(error => { + setDevices(devices); + setLoading(false); + console.log(error.response); + }); + } + } + + //Function called from the button on the table so that a user can see more details + const toggleDetails = (index) => { + const position = details.indexOf(index) + let newDetails = details.slice() + if (position !== -1) { + newDetails.splice(position, 1) + } else { + newDetails = [...details, index] + } + setDetails(newDetails) + } + + //refreshDevice() has a semicolon after to make it run once on component loading + useEffect(() => {refreshDevices();},[refreshDevices]); + + return ( + + ) +} + +const DeviceListDisplay = ({ devices, refresh, toggleDetails, details, loading, lastRefresh }) => { + const columns = [ + { key: 'serialNumber'}, + { key: 'UUID'}, + { key: 'lastConfigurationChange'}, + { key: 'lastConfigurationDownload'}, + { key: 'deviceType'}, + { key: 'connected'}, + { key: 'txBytes'}, + { key: 'rxBytes'}, + { key: 'ipAddress'}, + { + key: 'show_details', + label: '', + _style: { width: '1%' }, + sorter: false, + filter: false + } + ]; + + const getStatusBadge = (status)=>{ + if(status){ + return 'success'; + } + return 'danger'; + } + + return ( + <> +
+
+ + + +
+
+ Last refresh : {lastRefresh} +
+
+
+ ( + + {item.lastConfigurationChange.replace('T', ' ').replace('Z', '')} + + ), + 'lastConfigurationDownload': + (item)=>( + + {item.lastConfigurationDownload.replace('T', ' ').replace('Z', '')} + + ), + 'connected': + (item)=>( + + + {item.connected ? 'Connected' : 'Not connected'} + + + ), + 'show_details': + (item, index)=>{ + return ( + + {toggleDetails(index)}} + > + {details.includes(index) ? 'Hide' : 'Show'} + + + ) + }, + 'details': + (item, index)=>{ + return ( + + +

+ {item.notes} +

+

Last configuration change: {item.lastConfigurationChange.replace('T', ' ').replace('Z', '')}

+ `/devices/${item.serialNumber}`} + > + + Device Details + + + + + Reboot + +
+
+ ) + } + }} + /> + + ); +}; + + +export default DeviceList; \ No newline at end of file diff --git a/src/containers/TheContent.js b/src/containers/TheContent.js new file mode 100644 index 0000000..3a248b5 --- /dev/null +++ b/src/containers/TheContent.js @@ -0,0 +1,45 @@ +import React, { Suspense } from 'react' +import { + Redirect, + Route, + Switch +} from 'react-router-dom' +import { CContainer, CFade } from '@coreui/react' +import routes from '../routes' + +const loading = ( +
+
+
+) + +const TheContent = () => { + return ( +
+ + + + { + routes.map((route, idx) => { + return route.component && ( + ( + + + + )} /> + ) + })} + + + + +
+ ) +} + +export default React.memo(TheContent) diff --git a/src/containers/TheFooter.js b/src/containers/TheFooter.js new file mode 100644 index 0000000..b7fb4ef --- /dev/null +++ b/src/containers/TheFooter.js @@ -0,0 +1,19 @@ +import React from 'react' +import { CFooter } from '@coreui/react' + +const TheFooter = () => { + return ( + +
+ CoreUI + © 2020 creativeLabs. +
+
+ Powered by + CoreUI for React +
+
+ ) +} + +export default React.memo(TheFooter) diff --git a/src/containers/TheHeader.js b/src/containers/TheHeader.js new file mode 100644 index 0000000..8079f67 --- /dev/null +++ b/src/containers/TheHeader.js @@ -0,0 +1,78 @@ +import React from 'react' +import { useSelector, useDispatch } from 'react-redux' +import { logout } from '../utils/authHelper' +import { + CHeader, + CToggler, + CHeaderBrand, + CHeaderNav, + CSubheader, + CBreadcrumbRouter, + CLink +} from '@coreui/react' +import CIcon from '@coreui/icons-react' +import { cilAccountLogout} from '@coreui/icons'; +import routes from '../routes' + +const TheHeader = () => { + const dispatch = useDispatch() + const sidebarShow = useSelector(state => state.sidebarShow) + + const toggleSidebar = () => { + const val = [true, 'responsive'].includes(sidebarShow) ? false : 'responsive' + dispatch({type: 'set', sidebarShow: val}) + } + + const toggleSidebarMobile = () => { + const val = [false, 'responsive'].includes(sidebarShow) ? true : 'responsive' + dispatch({type: 'set', sidebarShow: val}) + } + + return ( + + + + + + + + + + + + + { logout();}} /> + + + + + +
+ +  Dashboard + + +  Settings + +
+
+
+ ) +} + +export default TheHeader diff --git a/src/containers/TheLayout.js b/src/containers/TheLayout.js new file mode 100644 index 0000000..e01cb9f --- /dev/null +++ b/src/containers/TheLayout.js @@ -0,0 +1,33 @@ +import React from 'react' +import { + TheContent, + TheSidebar, + TheFooter, + TheHeader +} from './index'; +import { useSelector } from 'react-redux'; + +const TheLayout = (props) => { + const { isLoggedIn } = useSelector(state => state.connected); + if(isLoggedIn){ + return ( +
+ {props.children} +
+ ); + } + return ( +
+ +
+ +
+ +
+ +
+
+ ) +} + +export default TheLayout diff --git a/src/containers/TheSidebar.js b/src/containers/TheSidebar.js new file mode 100644 index 0000000..19be4dd --- /dev/null +++ b/src/containers/TheSidebar.js @@ -0,0 +1,58 @@ +import React from 'react' +import { useSelector, useDispatch } from 'react-redux' +import { + CCreateElement, + CSidebar, + CSidebarBrand, + CSidebarNav, + CSidebarNavDivider, + CSidebarNavTitle, + CSidebarMinimizer, + CSidebarNavDropdown, + CSidebarNavItem, +} from '@coreui/react' + +import CIcon from '@coreui/icons-react' + +// sidebar nav config +import navigation from './_nav' + +const TheSidebar = () => { + const dispatch = useDispatch() + const show = useSelector(state => state.sidebarShow) + + return ( + dispatch({type: 'set', sidebarShow: val })} + > + + + + + + + + + + + ) +} + +export default React.memo(TheSidebar) diff --git a/src/containers/_nav.js b/src/containers/_nav.js new file mode 100644 index 0000000..1261898 --- /dev/null +++ b/src/containers/_nav.js @@ -0,0 +1,22 @@ +const _nav = [ + { + _tag: 'CSidebarNavItem', + name: 'List of Devices', + to: '/devices', + icon: 'cilNotes' + }, + { + _tag: 'CSidebarNavItem', + name: 'Create Device', + to: '/Create', + icon: 'cilPencil', + }, + { + _tag: 'CSidebarNavItem', + name: 'Settings', + to: '/Settings', + icon: 'cil-settings', + }, +] + +export default _nav diff --git a/src/containers/index.js b/src/containers/index.js new file mode 100644 index 0000000..1c9d3eb --- /dev/null +++ b/src/containers/index.js @@ -0,0 +1,13 @@ +import TheContent from './TheContent' +import TheFooter from './TheFooter' +import TheHeader from './TheHeader' +import TheLayout from './TheLayout' +import TheSidebar from './TheSidebar' + +export { + TheContent, + TheFooter, + TheHeader, + TheLayout, + TheSidebar +} diff --git a/src/features/counter/Counter.js b/src/features/counter/Counter.js deleted file mode 100644 index 772a6ba..0000000 --- a/src/features/counter/Counter.js +++ /dev/null @@ -1,67 +0,0 @@ -import React, { useState } from 'react'; -import { useSelector, useDispatch } from 'react-redux'; -import { - decrement, - increment, - incrementByAmount, - incrementAsync, - incrementIfOdd, - selectCount, -} from './counterSlice'; -import styles from './Counter.module.css'; - -export function Counter() { - const count = useSelector(selectCount); - const dispatch = useDispatch(); - const [incrementAmount, setIncrementAmount] = useState('2'); - - const incrementValue = Number(incrementAmount) || 0; - - return ( -
-
- - {count} - -
-
- setIncrementAmount(e.target.value)} - /> - - - -
-
- ); -} diff --git a/src/features/counter/Counter.module.css b/src/features/counter/Counter.module.css deleted file mode 100644 index 004ae33..0000000 --- a/src/features/counter/Counter.module.css +++ /dev/null @@ -1,78 +0,0 @@ -.row { - display: flex; - align-items: center; - justify-content: center; -} - -.row > button { - margin-left: 4px; - margin-right: 8px; -} -.row:not(:last-child) { - margin-bottom: 16px; -} - -.value { - font-size: 78px; - padding-left: 16px; - padding-right: 16px; - margin-top: 2px; - font-family: 'Courier New', Courier, monospace; -} - -.button { - appearance: none; - background: none; - font-size: 32px; - padding-left: 12px; - padding-right: 12px; - outline: none; - border: 2px solid transparent; - color: rgb(112, 76, 182); - padding-bottom: 4px; - cursor: pointer; - background-color: rgba(112, 76, 182, 0.1); - border-radius: 2px; - transition: all 0.15s; -} - -.textbox { - font-size: 32px; - padding: 2px; - width: 64px; - text-align: center; - margin-right: 4px; -} - -.button:hover, -.button:focus { - border: 2px solid rgba(112, 76, 182, 0.4); -} - -.button:active { - background-color: rgba(112, 76, 182, 0.2); -} - -.asyncButton { - composes: button; - position: relative; -} - -.asyncButton:after { - content: ''; - background-color: rgba(112, 76, 182, 0.15); - display: block; - position: absolute; - width: 100%; - height: 100%; - left: 0; - top: 0; - opacity: 0; - transition: width 1s linear, opacity 0.5s ease 1s; -} - -.asyncButton:active:after { - width: 0%; - opacity: 1; - transition: 0s; -} diff --git a/src/features/counter/counterAPI.js b/src/features/counter/counterAPI.js deleted file mode 100644 index cc9b4a4..0000000 --- a/src/features/counter/counterAPI.js +++ /dev/null @@ -1,6 +0,0 @@ -// A mock function to mimic making an async request for data -export function fetchCount(amount = 1) { - return new Promise((resolve) => - setTimeout(() => resolve({ data: amount }), 500) - ); -} diff --git a/src/features/counter/counterSlice.js b/src/features/counter/counterSlice.js deleted file mode 100644 index 8dc4b5c..0000000 --- a/src/features/counter/counterSlice.js +++ /dev/null @@ -1,73 +0,0 @@ -import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; -import { fetchCount } from './counterAPI'; - -const initialState = { - value: 0, - status: 'idle', -}; - -// The function below is called a thunk and allows us to perform async logic. It -// can be dispatched like a regular action: `dispatch(incrementAsync(10))`. This -// will call the thunk with the `dispatch` function as the first argument. Async -// code can then be executed and other actions can be dispatched. Thunks are -// typically used to make async requests. -export const incrementAsync = createAsyncThunk( - 'counter/fetchCount', - async (amount) => { - const response = await fetchCount(amount); - // The value we return becomes the `fulfilled` action payload - return response.data; - } -); - -export const counterSlice = createSlice({ - name: 'counter', - initialState, - // The `reducers` field lets us define reducers and generate associated actions - reducers: { - increment: (state) => { - // Redux Toolkit allows us to write "mutating" logic in reducers. It - // doesn't actually mutate the state because it uses the Immer library, - // which detects changes to a "draft state" and produces a brand new - // immutable state based off those changes - state.value += 1; - }, - decrement: (state) => { - state.value -= 1; - }, - // Use the PayloadAction type to declare the contents of `action.payload` - incrementByAmount: (state, action) => { - state.value += action.payload; - }, - }, - // The `extraReducers` field lets the slice handle actions defined elsewhere, - // including actions generated by createAsyncThunk or in other slices. - extraReducers: (builder) => { - builder - .addCase(incrementAsync.pending, (state) => { - state.status = 'loading'; - }) - .addCase(incrementAsync.fulfilled, (state, action) => { - state.status = 'idle'; - state.value += action.payload; - }); - }, -}); - -export const { increment, decrement, incrementByAmount } = counterSlice.actions; - -// The function below is called a selector and allows us to select a value from -// the state. Selectors can also be defined inline where they're used instead of -// in the slice file. For example: `useSelector((state: RootState) => state.counter.value)` -export const selectCount = (state) => state.counter.value; - -// We can also write thunks by hand, which may contain both sync and async logic. -// Here's an example of conditionally dispatching actions based on current state. -export const incrementIfOdd = (amount) => (dispatch, getState) => { - const currentValue = selectCount(getState()); - if (currentValue % 2 === 1) { - dispatch(incrementByAmount(amount)); - } -}; - -export default counterSlice.reducer; diff --git a/src/features/counter/counterSlice.spec.js b/src/features/counter/counterSlice.spec.js deleted file mode 100644 index c1fed2c..0000000 --- a/src/features/counter/counterSlice.spec.js +++ /dev/null @@ -1,33 +0,0 @@ -import counterReducer, { - increment, - decrement, - incrementByAmount, -} from './counterSlice'; - -describe('counter reducer', () => { - const initialState = { - value: 3, - status: 'idle', - }; - it('should handle initial state', () => { - expect(counterReducer(undefined, { type: 'unknown' })).toEqual({ - value: 0, - status: 'idle', - }); - }); - - it('should handle increment', () => { - const actual = counterReducer(initialState, increment()); - expect(actual.value).toEqual(4); - }); - - it('should handle decrement', () => { - const actual = counterReducer(initialState, decrement()); - expect(actual.value).toEqual(2); - }); - - it('should handle incrementByAmount', () => { - const actual = counterReducer(initialState, incrementByAmount(2)); - expect(actual.value).toEqual(5); - }); -}); diff --git a/src/index.js b/src/index.js index 9cc8015..65cfc40 100644 --- a/src/index.js +++ b/src/index.js @@ -1,21 +1,19 @@ import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; -import App from './App'; -import { store } from './app/store'; +import App from './App.js'; +import store from './store'; import { Provider } from 'react-redux'; -import * as serviceWorker from './serviceWorker'; +import 'bootstrap/dist/css/bootstrap.min.css' +import { icons } from './assets/icons' + +React.icons = icons; ReactDOM.render( - + , document.getElementById('root') ); - -// If you want your app to work offline and load faster, you can change -// unregister() to register() below. Note this comes with some pitfalls. -// Learn more about service workers: https://bit.ly/CRA-PWA -serviceWorker.unregister(); diff --git a/src/logo.svg b/src/logo.svg deleted file mode 100644 index 8466738..0000000 --- a/src/logo.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/routes.js b/src/routes.js new file mode 100644 index 0000000..346e2c2 --- /dev/null +++ b/src/routes.js @@ -0,0 +1,13 @@ +import React from 'react'; + +const DevicePage = React.lazy(() => import('./views/pages/DevicePage')); +const DeviceListPage = React.lazy(() => import('./views/pages/DeviceListPage')); + +const routes = [ + { path: '/devices', exact: true, name: 'Devices', component: DeviceListPage }, + { path: '/devices/:deviceId', name: 'Device Page', component: DevicePage }, + { path: '/Device', name: 'Device', component: DevicePage }, + { path: '/page2', name: 'Page2', component: DeviceListPage, exact: true }, +]; + + export default routes; \ No newline at end of file diff --git a/src/scss/_custom.scss b/src/scss/_custom.scss new file mode 100644 index 0000000..15d367a --- /dev/null +++ b/src/scss/_custom.scss @@ -0,0 +1 @@ +// Here you can add other styles diff --git a/src/scss/_fixes.scss b/src/scss/_fixes.scss new file mode 100644 index 0000000..93d3535 --- /dev/null +++ b/src/scss/_fixes.scss @@ -0,0 +1,16 @@ +// todo: disabled button styles +button { + &:disabled { + cursor: default; + } + &.disabled { + cursor: default; + } +} + +// todo: brand button icon margin +.btn-brand:not(:only-child) { + .c-icon { + margin-top: 0 !important; + } +} diff --git a/src/scss/_variables.scss b/src/scss/_variables.scss new file mode 100644 index 0000000..3ee3142 --- /dev/null +++ b/src/scss/_variables.scss @@ -0,0 +1 @@ +// Variable overrides diff --git a/src/scss/style.scss b/src/scss/style.scss new file mode 100644 index 0000000..e993a80 --- /dev/null +++ b/src/scss/style.scss @@ -0,0 +1,11 @@ +// If you want to override variables do it here +@import "variables"; + +// Import CoreUI styles +@import "~@coreui/coreui/scss/coreui.scss"; + +// Some temp fixes +@import "fixes"; + +// If you want to add something do it here +@import "custom"; diff --git a/src/store.js b/src/store.js new file mode 100644 index 0000000..5368716 --- /dev/null +++ b/src/store.js @@ -0,0 +1,20 @@ +import { createStore } from 'redux' + +const initialState = { + sidebarShow: 'responsive', + connected: false, + selectedDeviceId: null, + selectedDevice: null +} + +const changeState = (state = initialState, { type, ...rest }) => { + switch (type) { + case 'set': + return {...state, ...rest } + default: + return state + } +} + +const store = createStore(changeState) +export default store \ No newline at end of file diff --git a/src/utils/authHelper.js b/src/utils/authHelper.js new file mode 100644 index 0000000..e2f5299 --- /dev/null +++ b/src/utils/authHelper.js @@ -0,0 +1,13 @@ +export const logout = () => { + sessionStorage.clear(); + window.location.replace('/'); +} + +export const getToken = () => { + const token = sessionStorage.getItem('access_token'); + if (token === undefined || token === null) { + logout(); + return; + } + return token; +} \ No newline at end of file diff --git a/src/utils/axiosInstance.js b/src/utils/axiosInstance.js new file mode 100644 index 0000000..7796611 --- /dev/null +++ b/src/utils/axiosInstance.js @@ -0,0 +1,41 @@ +import * as axios from 'axios'; +import axiosRetry from 'axios-retry'; + +const axiosInstance = axios.create({ + baseURL: `${process.env.REACT_APP_BASE_URL}` +}); + +axiosRetry(axiosInstance , { + retries: 3, + retryDelay: (retryCount) => { + console.log(`retry attempt: ${retryCount}`); + return axiosRetry.exponentialDelay; + }, +}); + +axiosInstance.defaults.headers.get['Accept'] = 'application/json' // default header for all get request +axiosInstance.defaults.headers.post['Accept'] = 'application/json' // default header for all POST request + +axiosInstance.interceptors.response.use( + //Success actions + undefined, + function(error) { + console.log(error); + switch(error.response.status){ + case 401: + console.log('Error 401 ' + error ); + break; + case 403: + console.log('Error 403 ' + error ); + sessionStorage.clear(); + window.location.href = '/'; + break; + default: + console.log('Default ' + error.response.status); + break; + } + return Promise.reject(error); + } +); + +export default axiosInstance; \ No newline at end of file diff --git a/src/utils/helper.js b/src/utils/helper.js new file mode 100644 index 0000000..d22e11f --- /dev/null +++ b/src/utils/helper.js @@ -0,0 +1,3 @@ +export const cleanTimestamp = (timestamp) => { + return timestamp.replace('T', ' ').replace('Z', ' '); +} \ No newline at end of file diff --git a/src/views/icons/brands/Brands.js b/src/views/icons/brands/Brands.js new file mode 100644 index 0000000..e0cda1b --- /dev/null +++ b/src/views/icons/brands/Brands.js @@ -0,0 +1,36 @@ +import React from 'react' +import { CCard, CCardBody, CCardHeader, CCol, CRow } from '@coreui/react' +import CIcon from '@coreui/icons-react' +import { brandSet } from '@coreui/icons' +import { DocsLink } from 'src/reusable' + +const toKebabCase = (str) => { + return str.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, '$1-$2').toLowerCase() +} + +export const getIconsView = iconset => { + return Object.entries(iconset).map(([name, value]) => ( + + +
{toKebabCase(name)}
+
+ )) +} + +const CoreUIIcons = () => { + return ( + + + Brand Icons + + + + + {getIconsView(brandSet)} + + + + ) +} + +export default CoreUIIcons diff --git a/src/views/icons/coreui-icons/CoreUIIcons.js b/src/views/icons/coreui-icons/CoreUIIcons.js new file mode 100644 index 0000000..72c4d4d --- /dev/null +++ b/src/views/icons/coreui-icons/CoreUIIcons.js @@ -0,0 +1,23 @@ +import React from 'react' +import { CCard, CCardBody, CCardHeader, CRow } from '@coreui/react' +import { freeSet } from '@coreui/icons' +import { getIconsView } from '../brands/Brands.js' +import { DocsLink } from 'src/reusable' + +const CoreUIIcons = () => { + return ( + + + Free Icons / as CIcon{' '} + + + + + {getIconsView(freeSet)} + + + + ) +} + +export default CoreUIIcons diff --git a/src/views/icons/flags/Flags.js b/src/views/icons/flags/Flags.js new file mode 100644 index 0000000..157b401 --- /dev/null +++ b/src/views/icons/flags/Flags.js @@ -0,0 +1,23 @@ +import React from 'react' +import { CCard, CCardBody, CCardHeader, CRow } from '@coreui/react' +import { getIconsView } from '../brands/Brands.js' +import { flagSet } from '@coreui/icons' +import { DocsLink } from 'src/reusable' + +const CoreUIIcons = () => { + return ( + + + Flag Icons + + + + + {getIconsView(flagSet)} + + + + ) +} + +export default CoreUIIcons diff --git a/src/views/icons/index.js b/src/views/icons/index.js new file mode 100644 index 0000000..8b88ed3 --- /dev/null +++ b/src/views/icons/index.js @@ -0,0 +1,7 @@ +import CoreUIIcons from './coreui-icons'; +import Flags from './flags'; +import Brands from './brands'; + +export { + CoreUIIcons, Flags, Brands +}; diff --git a/src/views/pages/DeviceListPage.js b/src/views/pages/DeviceListPage.js new file mode 100644 index 0000000..ccf74f9 --- /dev/null +++ b/src/views/pages/DeviceListPage.js @@ -0,0 +1,12 @@ +import React from 'react'; +import DeviceList from '../../components/DeviceList'; + +const DeviceListPage = (props) => { + return ( +
+ +
+ ); +} + +export default DeviceListPage; \ No newline at end of file diff --git a/src/views/pages/DevicePage.js b/src/views/pages/DevicePage.js new file mode 100644 index 0000000..9b27f55 --- /dev/null +++ b/src/views/pages/DevicePage.js @@ -0,0 +1,124 @@ +import React, { useEffect, useState, useCallback } from 'react' +import { useDispatch, useSelector } from 'react-redux'; +import { useParams } from 'react-router-dom'; +import { + CRow, + CCol, + CButton, + } from '@coreui/react' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faSync } from '@fortawesome/free-solid-svg-icons'; +import axiosInstance from '../../utils/axiosInstance'; +import { getToken } from '../../utils/authHelper'; + +import DeviceHealth from '../../components/DeviceHealth'; +import DeviceConfiguration from '../../components/DeviceConfiguration'; +import DeviceActions from '../../components/DeviceActions' + +const DevicePage = (props) => { + const dispatch = useDispatch(); + const [device, setDevice] = useState([]); + const [lastRefresh, setLastRefresh] = useState([]); + + //Storing the deviceId in the store + let selectedDeviceId = useSelector(state => state.selectedDeviceId); + let { deviceId } = useParams(); + + if(!selectedDeviceId || selectedDeviceId !== deviceId){ + dispatch({type: 'set', selectedDeviceId: deviceId}); + selectedDeviceId = deviceId; + } + + const refreshDevice = useCallback(() => { + const addStatusToDevice = (device, options) => { + axiosInstance.get(`/device/${device.serialNumber}/status`, options) + .then((response) => { + device = {...device, ...response.data}; + device.ipAddress = device.ipAddress.substr(0, device.ipAddress.indexOf(':')); + }) + .catch(error => { + console.log(error.response); + }) + .finally (() => { + addHealthChecksToDevice(device, options); + }); + } + + const addHealthChecksToDevice = (device, options) => { + axiosInstance.get(`/device/${device.serialNumber}/healthchecks`, options) + .then((response) => { + device.healthChecks = response.data.values; + }) + .catch(error => { + console.log(error); + console.log(error.response); + }) + .finally(() => { + setDevice(device); + dispatch({type: 'set', selectedDevice: device}); + }); + } + + const options = { + headers : { + 'Accept': 'application/json', + 'Authorization': `Bearer ${getToken()}` + } + }; + + axiosInstance.get(`/device/${selectedDeviceId}`, options) + .then((response) => { + const date = new Date(); + const dateAsString = date.toLocaleString(); + setLastRefresh(dateAsString); + addStatusToDevice(response.data, options); + }) + .catch(error => { + console.log(error.response); + }); + }, [selectedDeviceId, dispatch]); + + + + useEffect(() => {refreshDevice();},[refreshDevice]); + + return ( + + ) +} + +const DeviceDisplay = ({ device, refresh, lastRefresh }) => { + return ( + <> +
+ + + + + + + +
+ Last refresh : {lastRefresh} +
+
+
+ + + + + + + + + +
+ + ); +} + +export default DevicePage; \ No newline at end of file diff --git a/src/views/pages/Login.js b/src/views/pages/Login.js new file mode 100644 index 0000000..2426970 --- /dev/null +++ b/src/views/pages/Login.js @@ -0,0 +1,92 @@ +import React, { useState } from 'react' +import { + CButton, + CCard, + CCardBody, + CCardGroup, + CCol, + CContainer, + CForm, + CInput, + CInputGroup, + CInputGroupPrepend, + CInputGroupText, + CRow +} from '@coreui/react' +import CIcon from '@coreui/icons-react' +import { cilUser, cilLockLocked} from '@coreui/icons'; +import { useDispatch } from 'react-redux'; +import axiosInstance from '../../utils/axiosInstance'; + + +const Login = () => { + const dispatch = useDispatch(); + const [userId, setUsername] = useState(''); + const [password, setPassword] = useState(''); + + const SignIn = (credentials) => { + axiosInstance.post('/oauth2', credentials) + .then((response) => { + sessionStorage.setItem('access_token', response.data.access_token); + dispatch({type: 'set', connected: true}); + }) + .catch(error => { + console.log(error.response); + }); + } + const formValidation = () => { + if (userId.trim() === '' || password.trim() === ''){ + return false; + } + return true; + }; + + return ( +
+ + + + + + + +

Login

+

Sign In to your account

+ + + + + + + setUsername(event.target.value)}/> + + + + + + + + setPassword(event.target.value)} /> + + + + formValidation() ? SignIn({ userId, password }) : alert('bad')}>Login + + + + Forgot password? + + +
+
+
+
+
+
+
+
+ ) +} + + export default Login + \ No newline at end of file