diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md index c884b2e12..3d9b31e2d 100644 --- a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md +++ b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md @@ -1,9 +1,14 @@ ## Describe your changes -## Issue Number if applicable +Provide a brief description of the changes you’ve made and their purpose. -## Checklist before requesting a review +## Issue number -- [ ] I have performed a self-review of my code -- [ ] I have inlcuded the issue # in the PR if applicable -- [ ] I have labelled the PR correctly +Mention the issue number(s) this PR addresses (e.g., #123). + +## Please ensure all items are checked off before requesting a review: + +- [ ] I have performed a self-review of my code. +- [ ] I have included the issue # in the PR. +- [ ] I have labelled the PR correctly. +- [ ] My PR is granular and targeted to one specific feature only. diff --git a/.prettierrc b/.prettierrc index 399423a21..5c345e37a 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,16 +1,16 @@ { - "printWidth": 90, - "useTabs": true, - "tabWidth": 2, - "singleQuote": false, - "bracketSpacing": true, - "proseWrap": "preserve", - "bracketSameLine": false, - "singleAttributePerLine": true, - "semi": true, - "jsx-single-quote": false, - "quoteProps": "as-needed", - "arrowParens": "always", - "trailingComma": "es5", - "htmlWhitespaceSensitivity": "css" + "printWidth": 90, + "useTabs": true, + "tabWidth": 2, + "singleQuote": false, + "bracketSpacing": true, + "proseWrap": "preserve", + "bracketSameLine": false, + "singleAttributePerLine": true, + "semi": true, + "jsxSingleQuote": false, + "quoteProps": "as-needed", + "arrowParens": "always", + "trailingComma": "es5", + "htmlWhitespaceSensitivity": "css" } diff --git a/Client/.eslintrc.cjs b/Client/.eslintrc.cjs index 3e212e1d4..3d78f264f 100644 --- a/Client/.eslintrc.cjs +++ b/Client/.eslintrc.cjs @@ -1,21 +1,18 @@ module.exports = { - root: true, - env: { browser: true, es2020: true }, - extends: [ - 'eslint:recommended', - 'plugin:react/recommended', - 'plugin:react/jsx-runtime', - 'plugin:react-hooks/recommended', - ], - ignorePatterns: ['dist', '.eslintrc.cjs'], - parserOptions: { ecmaVersion: 'latest', sourceType: 'module' }, - settings: { react: { version: '18.2' } }, - plugins: ['react-refresh'], - rules: { - 'react/jsx-no-target-blank': 'off', - 'react-refresh/only-export-components': [ - 'warn', - { allowConstantExport: true }, - ], - }, -} + root: true, + env: { browser: true, es2020: true }, + extends: [ + "eslint:recommended", + "plugin:react/recommended", + "plugin:react/jsx-runtime", + "plugin:react-hooks/recommended", + ], + ignorePatterns: ["dist", ".eslintrc.cjs"], + parserOptions: { ecmaVersion: "latest", sourceType: "module" }, + settings: { react: { version: "18.2" } }, + plugins: ["react-refresh"], + rules: { + "react/jsx-no-target-blank": "off", + "react-refresh/only-export-components": ["warn", { allowConstantExport: true }], + }, +}; diff --git a/Client/index.html b/Client/index.html index 3407fb5cd..8186ba31c 100644 --- a/Client/index.html +++ b/Client/index.html @@ -1,14 +1,23 @@ - + - - - - - BlueWave Uptime - + + + + + BlueWave Uptime + - -
- - + +
+ + diff --git a/Client/package-lock.json b/Client/package-lock.json index c78e9e33a..b66c62dd1 100644 --- a/Client/package-lock.json +++ b/Client/package-lock.json @@ -1,6503 +1,6454 @@ { - "name": "client", - "version": "0.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "client", - "version": "0.0.0", - "dependencies": { - "@emotion/react": "^11.11.4", - "@emotion/styled": "^11.11.5", - "@fontsource/roboto": "^5.0.13", - "@mui/icons-material": "^5.15.17", - "@mui/lab": "^5.0.0-alpha.170", - "@mui/material": "^5.15.16", - "@mui/x-charts": "^7.5.1", - "@mui/x-data-grid": "7.3.2", - "@mui/x-date-pickers": "7.3.2", - "@reduxjs/toolkit": "2.2.5", - "axios": "^1.7.4", - "chart.js": "^4.4.3", - "dayjs": "1.11.11", - "joi": "17.13.1", - "jwt-decode": "^4.0.0", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "react-redux": "9.1.2", - "react-router": "^6.23.0", - "react-router-dom": "^6.23.1", - "react-toastify": "^10.0.5", - "recharts": "2.13.0-alpha.4", - "redux-persist": "6.0.0", - "vite-plugin-svgr": "^4.2.0" - }, - "devDependencies": { - "@types/react": "^18.2.66", - "@types/react-dom": "^18.2.22", - "@vitejs/plugin-react": "^4.2.1", - "eslint": "^8.57.0", - "eslint-plugin-react": "^7.34.1", - "eslint-plugin-react-hooks": "^4.6.0", - "eslint-plugin-react-refresh": "^0.4.6", - "prettier": "^3.3.3", - "vite": "^5.2.0" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", - "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", - "license": "MIT", - "dependencies": { - "@babel/highlight": "^7.24.7", - "picocolors": "^1.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.24.9", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.9.tgz", - "integrity": "sha512-e701mcfApCJqMMueQI0Fb68Amflj83+dvAvHawoBpAz+GDjCIyGHzNwnefjsWJ3xiYAqqiQFoWbspGYBdb2/ng==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.24.9", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.9.tgz", - "integrity": "sha512-5e3FI4Q3M3Pbr21+5xJwCv6ZT6KmGkI0vw3Tozy5ODAQFTIWe37iT8Cr7Ice2Ntb+M3iSKCEWMB1MBgKrW3whg==", - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.24.9", - "@babel/helper-compilation-targets": "^7.24.8", - "@babel/helper-module-transforms": "^7.24.9", - "@babel/helpers": "^7.24.8", - "@babel/parser": "^7.24.8", - "@babel/template": "^7.24.7", - "@babel/traverse": "^7.24.8", - "@babel/types": "^7.24.9", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/core/node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "license": "MIT" - }, - "node_modules/@babel/generator": { - "version": "7.24.10", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.10.tgz", - "integrity": "sha512-o9HBZL1G2129luEUlG1hB4N/nlYNWHnpwlND9eOMclRqqu1YDy2sSYVCFUZwl8I1Gxh+QSRrP2vD7EpUmFVXxg==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.24.9", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", - "jsesc": "^2.5.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.8.tgz", - "integrity": "sha512-oU+UoqCHdp+nWVDkpldqIQL/i/bvAv53tRqLG/s+cOXxe66zOYLU7ar/Xs3LdmBihrUMEUhwu6dMZwbNOYDwvw==", - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.24.8", - "@babel/helper-validator-option": "^7.24.8", - "browserslist": "^4.23.1", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz", - "integrity": "sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz", - "integrity": "sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==", - "license": "MIT", - "dependencies": { - "@babel/template": "^7.24.7", - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz", - "integrity": "sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", - "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.24.9", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.9.tgz", - "integrity": "sha512-oYbh+rtFKj/HwBQkFlUzvcybzklmVdVV3UU+mN7n2t/q3yGHbuVdNxyFvSBO1tfvjyArpHNcWMAzsSPdyI46hw==", - "license": "MIT", - "dependencies": { - "@babel/helper-environment-visitor": "^7.24.7", - "@babel/helper-module-imports": "^7.24.7", - "@babel/helper-simple-access": "^7.24.7", - "@babel/helper-split-export-declaration": "^7.24.7", - "@babel/helper-validator-identifier": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz", - "integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-simple-access": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", - "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", - "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", - "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", - "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz", - "integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.8.tgz", - "integrity": "sha512-gV2265Nkcz7weJJfvDoAEVzC1e2OTDpkGbEsebse8koXUJUXPsCMi7sRo/+SPMuMZ9MtUPnGwITTnQnU5YjyaQ==", - "license": "MIT", - "dependencies": { - "@babel/template": "^7.24.7", - "@babel/types": "^7.24.8" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", - "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.24.7", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.8.tgz", - "integrity": "sha512-WzfbgXOkGzZiXXCqk43kKwZjzwx4oulxZi3nq2TYL9mOjQv6kYwul9mz6ID36njuL7Xkp6nJEfok848Zj10j/w==", - "license": "MIT", - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.24.7.tgz", - "integrity": "sha512-fOPQYbGSgH0HUp4UJO4sMBFjY6DuWq+2i8rixyUMb3CdGixs/gccURvYOAhajBdKDoGajFr3mUq5rH3phtkGzw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.24.7.tgz", - "integrity": "sha512-J2z+MWzZHVOemyLweMqngXrgGC42jQ//R0KdxqkIz/OrbVIIlhFI3WigZ5fO+nwFvBlncr4MGapd8vTyc7RPNQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/runtime": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.8.tgz", - "integrity": "sha512-5F7SDGs1T72ZczbRwbGO9lQi0NLjQxzl6i4lJxLxfW9U5UluCSyEJeniWvnhl3/euNiqQVbo8zruhsDfid0esA==", - "license": "MIT", - "dependencies": { - "regenerator-runtime": "^0.14.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/template": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz", - "integrity": "sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.24.7", - "@babel/parser": "^7.24.7", - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.8.tgz", - "integrity": "sha512-t0P1xxAPzEDcEPmjprAQq19NWum4K0EQPjMwZQZbHt+GiZqvjCHjj755Weq1YRPVzBI+3zSfvScfpnuIecVFJQ==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.24.8", - "@babel/helper-environment-visitor": "^7.24.7", - "@babel/helper-function-name": "^7.24.7", - "@babel/helper-hoist-variables": "^7.24.7", - "@babel/helper-split-export-declaration": "^7.24.7", - "@babel/parser": "^7.24.8", - "@babel/types": "^7.24.8", - "debug": "^4.3.1", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.24.9", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.9.tgz", - "integrity": "sha512-xm8XrMKz0IlUdocVbYJe0Z9xEgidU7msskG8BbhnTPK/HZ2z/7FP7ykqPgrUH+C+r414mNfNWam1f2vqOjqjYQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.24.8", - "@babel/helper-validator-identifier": "^7.24.7", - "to-fast-properties": "^2.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@emotion/babel-plugin": { - "version": "11.11.0", - "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz", - "integrity": "sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.16.7", - "@babel/runtime": "^7.18.3", - "@emotion/hash": "^0.9.1", - "@emotion/memoize": "^0.8.1", - "@emotion/serialize": "^1.1.2", - "babel-plugin-macros": "^3.1.0", - "convert-source-map": "^1.5.0", - "escape-string-regexp": "^4.0.0", - "find-root": "^1.1.0", - "source-map": "^0.5.7", - "stylis": "4.2.0" - } - }, - "node_modules/@emotion/cache": { - "version": "11.11.0", - "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.11.0.tgz", - "integrity": "sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==", - "license": "MIT", - "dependencies": { - "@emotion/memoize": "^0.8.1", - "@emotion/sheet": "^1.2.2", - "@emotion/utils": "^1.2.1", - "@emotion/weak-memoize": "^0.3.1", - "stylis": "4.2.0" - } - }, - "node_modules/@emotion/hash": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.1.tgz", - "integrity": "sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==", - "license": "MIT" - }, - "node_modules/@emotion/is-prop-valid": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz", - "integrity": "sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==", - "license": "MIT", - "dependencies": { - "@emotion/memoize": "^0.8.1" - } - }, - "node_modules/@emotion/memoize": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", - "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==", - "license": "MIT" - }, - "node_modules/@emotion/react": { - "version": "11.11.4", - "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.11.4.tgz", - "integrity": "sha512-t8AjMlF0gHpvvxk5mAtCqR4vmxiGHCeJBaQO6gncUSdklELOgtwjerNY2yuJNfwnc6vi16U/+uMF+afIawJ9iw==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.18.3", - "@emotion/babel-plugin": "^11.11.0", - "@emotion/cache": "^11.11.0", - "@emotion/serialize": "^1.1.3", - "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1", - "@emotion/utils": "^1.2.1", - "@emotion/weak-memoize": "^0.3.1", - "hoist-non-react-statics": "^3.3.1" - }, - "peerDependencies": { - "react": ">=16.8.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@emotion/serialize": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.4.tgz", - "integrity": "sha512-RIN04MBT8g+FnDwgvIUi8czvr1LU1alUMI05LekWB5DGyTm8cCBMCRpq3GqaiyEDRptEXOyXnvZ58GZYu4kBxQ==", - "license": "MIT", - "dependencies": { - "@emotion/hash": "^0.9.1", - "@emotion/memoize": "^0.8.1", - "@emotion/unitless": "^0.8.1", - "@emotion/utils": "^1.2.1", - "csstype": "^3.0.2" - } - }, - "node_modules/@emotion/sheet": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.2.tgz", - "integrity": "sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==", - "license": "MIT" - }, - "node_modules/@emotion/styled": { - "version": "11.11.5", - "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.11.5.tgz", - "integrity": "sha512-/ZjjnaNKvuMPxcIiUkf/9SHoG4Q196DRl1w82hQ3WCsjo1IUR8uaGWrC6a87CrYAW0Kb/pK7hk8BnLgLRi9KoQ==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.18.3", - "@emotion/babel-plugin": "^11.11.0", - "@emotion/is-prop-valid": "^1.2.2", - "@emotion/serialize": "^1.1.4", - "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1", - "@emotion/utils": "^1.2.1" - }, - "peerDependencies": { - "@emotion/react": "^11.0.0-rc.0", - "react": ">=16.8.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@emotion/unitless": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", - "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==", - "license": "MIT" - }, - "node_modules/@emotion/use-insertion-effect-with-fallbacks": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz", - "integrity": "sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==", - "license": "MIT", - "peerDependencies": { - "react": ">=16.8.0" - } - }, - "node_modules/@emotion/utils": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.1.tgz", - "integrity": "sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==", - "license": "MIT" - }, - "node_modules/@emotion/weak-memoize": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz", - "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==", - "license": "MIT" - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", - "cpu": [ - "mips64el" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz", - "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@eslint/js": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", - "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/@floating-ui/core": { - "version": "1.6.4", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.4.tgz", - "integrity": "sha512-a4IowK4QkXl4SCWTGUR0INAfEOX3wtsYw3rKK5InQEHMGObkR8Xk44qYQD9P4r6HHw0iIfK6GUKECmY8sTkqRA==", - "license": "MIT", - "dependencies": { - "@floating-ui/utils": "^0.2.4" - } - }, - "node_modules/@floating-ui/dom": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.7.tgz", - "integrity": "sha512-wmVfPG5o2xnKDU4jx/m4w5qva9FWHcnZ8BvzEe90D/RpwsJaTAVYPEPdQ8sbr/N8zZTAHlZUTQdqg8ZUbzHmng==", - "license": "MIT", - "dependencies": { - "@floating-ui/core": "^1.6.0", - "@floating-ui/utils": "^0.2.4" - } - }, - "node_modules/@floating-ui/react-dom": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.1.tgz", - "integrity": "sha512-4h84MJt3CHrtG18mGsXuLCHMrug49d7DFkU0RMIyshRveBeyV2hmV/pDaF2Uxtu8kgq5r46llp5E5FQiR0K2Yg==", - "license": "MIT", - "dependencies": { - "@floating-ui/dom": "^1.0.0" - }, - "peerDependencies": { - "react": ">=16.8.0", - "react-dom": ">=16.8.0" - } - }, - "node_modules/@floating-ui/utils": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.4.tgz", - "integrity": "sha512-dWO2pw8hhi+WrXq1YJy2yCuWoL20PddgGaqTgVe4cOS9Q6qklXCiA1tJEqX6BEwRNSCP84/afac9hd4MS+zEUA==", - "license": "MIT" - }, - "node_modules/@fontsource/roboto": { - "version": "5.0.13", - "resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-5.0.13.tgz", - "integrity": "sha512-j61DHjsdUCKMXSdNLTOxcG701FWnF0jcqNNQi2iPCDxU8seN/sMxeh62dC++UiagCWq9ghTypX+Pcy7kX+QOeQ==", - "license": "Apache-2.0" - }, - "node_modules/@hapi/hoek": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", - "license": "BSD-3-Clause" - }, - "node_modules/@hapi/topo": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", - "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", - "license": "BSD-3-Clause", - "dependencies": { - "@hapi/hoek": "^9.0.0" - } - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.11.14", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", - "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", - "deprecated": "Use @eslint/config-array instead", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanwhocodes/object-schema": "^2.0.2", - "debug": "^4.3.1", - "minimatch": "^3.0.5" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "deprecated": "Use @eslint/object-schema instead", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", - "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", - "license": "MIT", - "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@kurkle/color": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz", - "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==", - "license": "MIT" - }, - "node_modules/@mui/base": { - "version": "5.0.0-beta.40", - "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.40.tgz", - "integrity": "sha512-I/lGHztkCzvwlXpjD2+SNmvNQvB4227xBXhISPjEaJUXGImOQ9f3D2Yj/T3KasSI/h0MLWy74X0J6clhPmsRbQ==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.23.9", - "@floating-ui/react-dom": "^2.0.8", - "@mui/types": "^7.2.14", - "@mui/utils": "^5.15.14", - "@popperjs/core": "^2.11.8", - "clsx": "^2.1.0", - "prop-types": "^15.8.1" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0", - "react-dom": "^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@mui/core-downloads-tracker": { - "version": "5.16.4", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.16.4.tgz", - "integrity": "sha512-rNdHXhclwjEZnK+//3SR43YRx0VtjdHnUFhMSGYmAMJve+KiwEja/41EYh8V3pZKqF2geKyfcFUenTfDTYUR4w==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - } - }, - "node_modules/@mui/icons-material": { - "version": "5.16.4", - "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.16.4.tgz", - "integrity": "sha512-j9/CWctv6TH6Dou2uR2EH7UOgu79CW/YcozxCYVLJ7l03pCsiOlJ5sBArnWJxJ+nGkFwyL/1d1k8JEPMDR125A==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.23.9" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@mui/material": "^5.0.0", - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@mui/lab": { - "version": "5.0.0-alpha.172", - "resolved": "https://registry.npmjs.org/@mui/lab/-/lab-5.0.0-alpha.172.tgz", - "integrity": "sha512-stpa3WTsDE1HamFR4eeS6Bhxalm+u9FhzzNph/PrDMdWSRBHlJs2mqvZ6FEoO22O7MOCwNMqbXTkvEwsyEf0ew==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.23.9", - "@mui/base": "5.0.0-beta.40", - "@mui/system": "^5.16.1", - "@mui/types": "^7.2.15", - "@mui/utils": "^5.16.1", - "clsx": "^2.1.0", - "prop-types": "^15.8.1" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@emotion/react": "^11.5.0", - "@emotion/styled": "^11.3.0", - "@mui/material": ">=5.15.0", - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0", - "react-dom": "^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@emotion/react": { - "optional": true - }, - "@emotion/styled": { - "optional": true - }, - "@types/react": { - "optional": true - } - } - }, - "node_modules/@mui/material": { - "version": "5.16.4", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.16.4.tgz", - "integrity": "sha512-dBnh3/zRYgEVIS3OE4oTbujse3gifA0qLMmuUk13ywsDCbngJsdgwW5LuYeiT5pfA8PGPGSqM7mxNytYXgiMCw==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.23.9", - "@mui/core-downloads-tracker": "^5.16.4", - "@mui/system": "^5.16.4", - "@mui/types": "^7.2.15", - "@mui/utils": "^5.16.4", - "@popperjs/core": "^2.11.8", - "@types/react-transition-group": "^4.4.10", - "clsx": "^2.1.0", - "csstype": "^3.1.3", - "prop-types": "^15.8.1", - "react-is": "^18.3.1", - "react-transition-group": "^4.4.5" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@emotion/react": "^11.5.0", - "@emotion/styled": "^11.3.0", - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0", - "react-dom": "^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@emotion/react": { - "optional": true - }, - "@emotion/styled": { - "optional": true - }, - "@types/react": { - "optional": true - } - } - }, - "node_modules/@mui/private-theming": { - "version": "5.16.4", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.16.4.tgz", - "integrity": "sha512-ZsAm8cq31SJ37SVWLRlu02v9SRthxnfQofaiv14L5Bht51B0dz6yQEoVU/V8UduZDCCIrWkBHuReVfKhE/UuXA==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.23.9", - "@mui/utils": "^5.16.4", - "prop-types": "^15.8.1" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@mui/styled-engine": { - "version": "5.16.4", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.16.4.tgz", - "integrity": "sha512-0+mnkf+UiAmTVB8PZFqOhqf729Yh0Cxq29/5cA3VAyDVTRIUUQ8FXQhiAhUIbijFmM72rY80ahFPXIm4WDbzcA==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.23.9", - "@emotion/cache": "^11.11.0", - "csstype": "^3.1.3", - "prop-types": "^15.8.1" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@emotion/react": "^11.4.1", - "@emotion/styled": "^11.3.0", - "react": "^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@emotion/react": { - "optional": true - }, - "@emotion/styled": { - "optional": true - } - } - }, - "node_modules/@mui/system": { - "version": "5.16.4", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.16.4.tgz", - "integrity": "sha512-ET1Ujl2/8hbsD611/mqUuNArMCGv/fIWO/f8B3ZqF5iyPHM2aS74vhTNyjytncc4i6dYwGxNk+tLa7GwjNS0/w==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.23.9", - "@mui/private-theming": "^5.16.4", - "@mui/styled-engine": "^5.16.4", - "@mui/types": "^7.2.15", - "@mui/utils": "^5.16.4", - "clsx": "^2.1.0", - "csstype": "^3.1.3", - "prop-types": "^15.8.1" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@emotion/react": "^11.5.0", - "@emotion/styled": "^11.3.0", - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@emotion/react": { - "optional": true - }, - "@emotion/styled": { - "optional": true - }, - "@types/react": { - "optional": true - } - } - }, - "node_modules/@mui/types": { - "version": "7.2.15", - "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.15.tgz", - "integrity": "sha512-nbo7yPhtKJkdf9kcVOF8JZHPZTmqXjJ/tI0bdWgHg5tp9AnIN4Y7f7wm9T+0SyGYJk76+GYZ8Q5XaTYAsUHN0Q==", - "license": "MIT", - "peerDependencies": { - "@types/react": "^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@mui/utils": { - "version": "5.16.4", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.16.4.tgz", - "integrity": "sha512-nlppYwq10TBIFqp7qxY0SvbACOXeOjeVL3pOcDsK0FT8XjrEXh9/+lkg8AEIzD16z7YfiJDQjaJG2OLkE7BxNg==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.23.9", - "@types/prop-types": "^15.7.12", - "clsx": "^2.1.1", - "prop-types": "^15.8.1", - "react-is": "^18.3.1" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@mui/x-charts": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/@mui/x-charts/-/x-charts-7.10.0.tgz", - "integrity": "sha512-k5dGcc2IIVXWbWs+mWLPqngTg960UkvUOvBne3724hy4PzuIZpCi5kICB0Lb2uMJ9xKZwAzzbjcbYKa4F7+/NA==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.24.7", - "@mui/base": "^5.0.0-beta.40", - "@mui/system": "^5.16.0", - "@mui/utils": "^5.16.0", - "@react-spring/rafz": "^9.7.3", - "@react-spring/web": "^9.7.3", - "clsx": "^2.1.1", - "d3-color": "^3.1.0", - "d3-delaunay": "^6.0.4", - "d3-interpolate": "^3.0.1", - "d3-scale": "^4.0.2", - "d3-shape": "^3.2.0", - "prop-types": "^15.8.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "@emotion/react": "^11.9.0", - "@emotion/styled": "^11.8.1", - "@mui/material": "^5.15.14", - "react": "^17.0.0 || ^18.0.0", - "react-dom": "^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@emotion/react": { - "optional": true - }, - "@emotion/styled": { - "optional": true - } - } - }, - "node_modules/@mui/x-data-grid": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-7.3.2.tgz", - "integrity": "sha512-seuRiZ2yyhzeUa7Thzap0xvvizGPSEwJRNOjY9kffjUr+0iXGF3PZGEsMoJ7jCjZ2peHX7FjfqBdssDvizxIDQ==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.24.0", - "@mui/system": "^5.15.14", - "@mui/utils": "^5.15.14", - "clsx": "^2.1.0", - "prop-types": "^15.8.1", - "reselect": "^4.1.8" - }, - "engines": { - "node": ">=14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@mui/material": "^5.15.14", - "react": "^17.0.0 || ^18.0.0", - "react-dom": "^17.0.0 || ^18.0.0" - } - }, - "node_modules/@mui/x-date-pickers": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-7.3.2.tgz", - "integrity": "sha512-i7JaDs1eXSZWyJihfszUHVV0t/C2HvtdMv5tHwv3E3enMx5Hup1vkJ64vZAH2fgGrTHQH8mjxvVsmI6jhDXIUg==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.24.0", - "@mui/base": "^5.0.0-beta.40", - "@mui/system": "^5.15.14", - "@mui/utils": "^5.15.14", - "@types/react-transition-group": "^4.4.10", - "clsx": "^2.1.0", - "prop-types": "^15.8.1", - "react-transition-group": "^4.4.5" - }, - "engines": { - "node": ">=14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@emotion/react": "^11.9.0", - "@emotion/styled": "^11.8.1", - "@mui/material": "^5.15.14", - "date-fns": "^2.25.0 || ^3.2.0", - "date-fns-jalali": "^2.13.0-0", - "dayjs": "^1.10.7", - "luxon": "^3.0.2", - "moment": "^2.29.4", - "moment-hijri": "^2.1.2", - "moment-jalaali": "^0.7.4 || ^0.8.0 || ^0.9.0 || ^0.10.0", - "react": "^17.0.0 || ^18.0.0", - "react-dom": "^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@emotion/react": { - "optional": true - }, - "@emotion/styled": { - "optional": true - }, - "date-fns": { - "optional": true - }, - "date-fns-jalali": { - "optional": true - }, - "dayjs": { - "optional": true - }, - "luxon": { - "optional": true - }, - "moment": { - "optional": true - }, - "moment-hijri": { - "optional": true - }, - "moment-jalaali": { - "optional": true - } - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@popperjs/core": { - "version": "2.11.8", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", - "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/popperjs" - } - }, - "node_modules/@react-spring/animated": { - "version": "9.7.3", - "resolved": "https://registry.npmjs.org/@react-spring/animated/-/animated-9.7.3.tgz", - "integrity": "sha512-5CWeNJt9pNgyvuSzQH+uy2pvTg8Y4/OisoscZIR8/ZNLIOI+CatFBhGZpDGTF/OzdNFsAoGk3wiUYTwoJ0YIvw==", - "license": "MIT", - "dependencies": { - "@react-spring/shared": "~9.7.3", - "@react-spring/types": "~9.7.3" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/@react-spring/core": { - "version": "9.7.3", - "resolved": "https://registry.npmjs.org/@react-spring/core/-/core-9.7.3.tgz", - "integrity": "sha512-IqFdPVf3ZOC1Cx7+M0cXf4odNLxDC+n7IN3MDcVCTIOSBfqEcBebSv+vlY5AhM0zw05PDbjKrNmBpzv/AqpjnQ==", - "license": "MIT", - "dependencies": { - "@react-spring/animated": "~9.7.3", - "@react-spring/shared": "~9.7.3", - "@react-spring/types": "~9.7.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/react-spring/donate" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/@react-spring/rafz": { - "version": "9.7.3", - "resolved": "https://registry.npmjs.org/@react-spring/rafz/-/rafz-9.7.3.tgz", - "integrity": "sha512-9vzW1zJPcC4nS3aCV+GgcsK/WLaB520Iyvm55ARHfM5AuyBqycjvh1wbmWmgCyJuX4VPoWigzemq1CaaeRSHhQ==", - "license": "MIT" - }, - "node_modules/@react-spring/shared": { - "version": "9.7.3", - "resolved": "https://registry.npmjs.org/@react-spring/shared/-/shared-9.7.3.tgz", - "integrity": "sha512-NEopD+9S5xYyQ0pGtioacLhL2luflh6HACSSDUZOwLHoxA5eku1UPuqcJqjwSD6luKjjLfiLOspxo43FUHKKSA==", - "license": "MIT", - "dependencies": { - "@react-spring/types": "~9.7.3" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/@react-spring/types": { - "version": "9.7.3", - "resolved": "https://registry.npmjs.org/@react-spring/types/-/types-9.7.3.tgz", - "integrity": "sha512-Kpx/fQ/ZFX31OtlqVEFfgaD1ACzul4NksrvIgYfIFq9JpDHFwQkMVZ10tbo0FU/grje4rcL4EIrjekl3kYwgWw==", - "license": "MIT" - }, - "node_modules/@react-spring/web": { - "version": "9.7.3", - "resolved": "https://registry.npmjs.org/@react-spring/web/-/web-9.7.3.tgz", - "integrity": "sha512-BXt6BpS9aJL/QdVqEIX9YoUy8CE6TJrU0mNCqSoxdXlIeNcEBWOfIyE6B14ENNsyQKS3wOWkiJfco0tCr/9tUg==", - "license": "MIT", - "dependencies": { - "@react-spring/animated": "~9.7.3", - "@react-spring/core": "~9.7.3", - "@react-spring/shared": "~9.7.3", - "@react-spring/types": "~9.7.3" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/@reduxjs/toolkit": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.2.5.tgz", - "integrity": "sha512-aeFA/s5NCG7NoJe/MhmwREJxRkDs0ZaSqt0MxhWUrwCf1UQXpwR87RROJEql0uAkLI6U7snBOYOcKw83ew3FPg==", - "license": "MIT", - "dependencies": { - "immer": "^10.0.3", - "redux": "^5.0.1", - "redux-thunk": "^3.1.0", - "reselect": "^5.1.0" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18", - "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" - }, - "peerDependenciesMeta": { - "react": { - "optional": true - }, - "react-redux": { - "optional": true - } - } - }, - "node_modules/@reduxjs/toolkit/node_modules/reselect": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", - "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", - "license": "MIT" - }, - "node_modules/@remix-run/router": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.18.0.tgz", - "integrity": "sha512-L3jkqmqoSVBVKHfpGZmLrex0lxR5SucGA0sUfFzGctehw+S/ggL9L/0NnC5mw6P8HUWpFZ3nQw3cRApjjWx9Sw==", - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@rollup/pluginutils": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz", - "integrity": "sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "estree-walker": "^2.0.2", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.4.tgz", - "integrity": "sha512-Fxamp4aEZnfPOcGA8KSNEohV8hX7zVHOemC8jVBoBUHu5zpJK/Eu3uJwt6BMgy9fkvzxDaurgj96F/NiLukF2w==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.22.4.tgz", - "integrity": "sha512-VXoK5UMrgECLYaMuGuVTOx5kcuap1Jm8g/M83RnCHBKOqvPPmROFJGQaZhGccnsFtfXQ3XYa4/jMCJvZnbJBdA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.22.4.tgz", - "integrity": "sha512-xMM9ORBqu81jyMKCDP+SZDhnX2QEVQzTcC6G18KlTQEzWK8r/oNZtKuZaCcHhnsa6fEeOBionoyl5JsAbE/36Q==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.22.4.tgz", - "integrity": "sha512-aJJyYKQwbHuhTUrjWjxEvGnNNBCnmpHDvrb8JFDbeSH3m2XdHcxDd3jthAzvmoI8w/kSjd2y0udT+4okADsZIw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.22.4.tgz", - "integrity": "sha512-j63YtCIRAzbO+gC2L9dWXRh5BFetsv0j0va0Wi9epXDgU/XUi5dJKo4USTttVyK7fGw2nPWK0PbAvyliz50SCQ==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.22.4.tgz", - "integrity": "sha512-dJnWUgwWBX1YBRsuKKMOlXCzh2Wu1mlHzv20TpqEsfdZLb3WoJW2kIEsGwLkroYf24IrPAvOT/ZQ2OYMV6vlrg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.22.4.tgz", - "integrity": "sha512-AdPRoNi3NKVLolCN/Sp4F4N1d98c4SBnHMKoLuiG6RXgoZ4sllseuGioszumnPGmPM2O7qaAX/IJdeDU8f26Aw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.22.4.tgz", - "integrity": "sha512-Gl0AxBtDg8uoAn5CCqQDMqAx22Wx22pjDOjBdmG0VIWX3qUBHzYmOKh8KXHL4UpogfJ14G4wk16EQogF+v8hmA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.22.4.tgz", - "integrity": "sha512-3aVCK9xfWW1oGQpTsYJJPF6bfpWfhbRnhdlyhak2ZiyFLDaayz0EP5j9V1RVLAAxlmWKTDfS9wyRyY3hvhPoOg==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.22.4.tgz", - "integrity": "sha512-ePYIir6VYnhgv2C5Xe9u+ico4t8sZWXschR6fMgoPUK31yQu7hTEJb7bCqivHECwIClJfKgE7zYsh1qTP3WHUA==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.22.4.tgz", - "integrity": "sha512-GqFJ9wLlbB9daxhVlrTe61vJtEY99/xB3C8e4ULVsVfflcpmR6c8UZXjtkMA6FhNONhj2eA5Tk9uAVw5orEs4Q==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.22.4.tgz", - "integrity": "sha512-87v0ol2sH9GE3cLQLNEy0K/R0pz1nvg76o8M5nhMR0+Q+BBGLnb35P0fVz4CQxHYXaAOhE8HhlkaZfsdUOlHwg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.22.4.tgz", - "integrity": "sha512-UV6FZMUgePDZrFjrNGIWzDo/vABebuXBhJEqrHxrGiU6HikPy0Z3LfdtciIttEUQfuDdCn8fqh7wiFJjCNwO+g==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.22.4.tgz", - "integrity": "sha512-BjI+NVVEGAXjGWYHz/vv0pBqfGoUH0IGZ0cICTn7kB9PyjrATSkX+8WkguNjWoj2qSr1im/+tTGRaY+4/PdcQw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.22.4.tgz", - "integrity": "sha512-SiWG/1TuUdPvYmzmYnmd3IEifzR61Tragkbx9D3+R8mzQqDBz8v+BvZNDlkiTtI9T15KYZhP0ehn3Dld4n9J5g==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.22.4.tgz", - "integrity": "sha512-j8pPKp53/lq9lMXN57S8cFz0MynJk8OWNuUnXct/9KCpKU7DgU3bYMJhwWmcqC0UU29p8Lr0/7KEVcaM6bf47Q==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@sideway/address": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", - "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", - "license": "BSD-3-Clause", - "dependencies": { - "@hapi/hoek": "^9.0.0" - } - }, - "node_modules/@sideway/formula": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", - "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", - "license": "BSD-3-Clause" - }, - "node_modules/@sideway/pinpoint": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", - "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", - "license": "BSD-3-Clause" - }, - "node_modules/@svgr/babel-plugin-add-jsx-attribute": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", - "integrity": "sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==", - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@svgr/babel-plugin-remove-jsx-attribute": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz", - "integrity": "sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==", - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@svgr/babel-plugin-remove-jsx-empty-expression": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz", - "integrity": "sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==", - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@svgr/babel-plugin-replace-jsx-attribute-value": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz", - "integrity": "sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==", - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@svgr/babel-plugin-svg-dynamic-title": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz", - "integrity": "sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==", - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@svgr/babel-plugin-svg-em-dimensions": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz", - "integrity": "sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==", - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@svgr/babel-plugin-transform-react-native-svg": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz", - "integrity": "sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==", - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@svgr/babel-plugin-transform-svg-component": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz", - "integrity": "sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@svgr/babel-preset": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-8.1.0.tgz", - "integrity": "sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==", - "license": "MIT", - "dependencies": { - "@svgr/babel-plugin-add-jsx-attribute": "8.0.0", - "@svgr/babel-plugin-remove-jsx-attribute": "8.0.0", - "@svgr/babel-plugin-remove-jsx-empty-expression": "8.0.0", - "@svgr/babel-plugin-replace-jsx-attribute-value": "8.0.0", - "@svgr/babel-plugin-svg-dynamic-title": "8.0.0", - "@svgr/babel-plugin-svg-em-dimensions": "8.0.0", - "@svgr/babel-plugin-transform-react-native-svg": "8.1.0", - "@svgr/babel-plugin-transform-svg-component": "8.0.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@svgr/core": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz", - "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", - "license": "MIT", - "dependencies": { - "@babel/core": "^7.21.3", - "@svgr/babel-preset": "8.1.0", - "camelcase": "^6.2.0", - "cosmiconfig": "^8.1.3", - "snake-case": "^3.0.4" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } - }, - "node_modules/@svgr/core/node_modules/cosmiconfig": { - "version": "8.3.6", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", - "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", - "license": "MIT", - "dependencies": { - "import-fresh": "^3.3.0", - "js-yaml": "^4.1.0", - "parse-json": "^5.2.0", - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/d-fischer" - }, - "peerDependencies": { - "typescript": ">=4.9.5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@svgr/hast-util-to-babel-ast": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz", - "integrity": "sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.21.3", - "entities": "^4.4.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - } - }, - "node_modules/@svgr/plugin-jsx": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz", - "integrity": "sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==", - "license": "MIT", - "dependencies": { - "@babel/core": "^7.21.3", - "@svgr/babel-preset": "8.1.0", - "@svgr/hast-util-to-babel-ast": "8.0.0", - "svg-parser": "^2.0.4" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/gregberge" - }, - "peerDependencies": { - "@svgr/core": "*" - } - }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.6.8", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", - "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.20.6", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", - "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.20.7" - } - }, - "node_modules/@types/d3-array": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz", - "integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==", - "license": "MIT" - }, - "node_modules/@types/d3-color": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", - "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", - "license": "MIT" - }, - "node_modules/@types/d3-ease": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", - "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", - "license": "MIT" - }, - "node_modules/@types/d3-interpolate": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", - "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", - "license": "MIT", - "dependencies": { - "@types/d3-color": "*" - } - }, - "node_modules/@types/d3-path": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.0.tgz", - "integrity": "sha512-P2dlU/q51fkOc/Gfl3Ul9kicV7l+ra934qBFXCFhrZMOL6du1TM0pm1ThYvENukyOn5h9v+yMJ9Fn5JK4QozrQ==", - "license": "MIT" - }, - "node_modules/@types/d3-scale": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.8.tgz", - "integrity": "sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==", - "license": "MIT", - "dependencies": { - "@types/d3-time": "*" - } - }, - "node_modules/@types/d3-shape": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.6.tgz", - "integrity": "sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA==", - "license": "MIT", - "dependencies": { - "@types/d3-path": "*" - } - }, - "node_modules/@types/d3-time": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.3.tgz", - "integrity": "sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw==", - "license": "MIT" - }, - "node_modules/@types/d3-timer": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", - "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", - "license": "MIT" - }, - "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", - "license": "MIT" - }, - "node_modules/@types/parse-json": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", - "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", - "license": "MIT" - }, - "node_modules/@types/prop-types": { - "version": "15.7.12", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", - "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==", - "license": "MIT" - }, - "node_modules/@types/react": { - "version": "18.3.3", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz", - "integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==", - "license": "MIT", - "dependencies": { - "@types/prop-types": "*", - "csstype": "^3.0.2" - } - }, - "node_modules/@types/react-dom": { - "version": "18.3.0", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz", - "integrity": "sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/react": "*" - } - }, - "node_modules/@types/react-transition-group": { - "version": "4.4.10", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.10.tgz", - "integrity": "sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==", - "license": "MIT", - "dependencies": { - "@types/react": "*" - } - }, - "node_modules/@types/use-sync-external-store": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", - "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==", - "license": "MIT" - }, - "node_modules/@ungap/structured-clone": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", - "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/@vitejs/plugin-react": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.1.tgz", - "integrity": "sha512-m/V2syj5CuVnaxcUJOQRel/Wr31FFXRFlnOoq1TVtkCxsY5veGMTEmpWHndrhB2U8ScHtCQB1e+4hWYExQc6Lg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.24.5", - "@babel/plugin-transform-react-jsx-self": "^7.24.5", - "@babel/plugin-transform-react-jsx-source": "^7.24.1", - "@types/babel__core": "^7.20.5", - "react-refresh": "^0.14.2" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "peerDependencies": { - "vite": "^4.2.0 || ^5.0.0" - } - }, - "node_modules/acorn": { - "version": "8.12.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", - "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/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==", - "license": "MIT", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "license": "Python-2.0" - }, - "node_modules/array-buffer-byte-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", - "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.5", - "is-array-buffer": "^3.0.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array-includes": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", - "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.4", - "is-string": "^1.0.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.findlast": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", - "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "es-shim-unscopables": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.flat": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", - "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.flatmap": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", - "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.toreversed": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/array.prototype.toreversed/-/array.prototype.toreversed-1.1.2.tgz", - "integrity": "sha512-wwDCoT4Ck4Cz7sLtgUmzR5UV3YF5mFHUlbChCzZBQZ+0m2cl/DH3tKgvphv1nKgFsJ48oCSg6p91q2Vm0I/ZMA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0" - } - }, - "node_modules/array.prototype.tosorted": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", - "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.3", - "es-errors": "^1.3.0", - "es-shim-unscopables": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", - "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-buffer-byte-length": "^1.0.1", - "call-bind": "^1.0.5", - "define-properties": "^1.2.1", - "es-abstract": "^1.22.3", - "es-errors": "^1.2.1", - "get-intrinsic": "^1.2.3", - "is-array-buffer": "^3.0.4", - "is-shared-array-buffer": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "license": "MIT" - }, - "node_modules/available-typed-arrays": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", - "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "possible-typed-array-names": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/axios": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz", - "integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==", - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, - "node_modules/babel-plugin-macros": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", - "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.12.5", - "cosmiconfig": "^7.0.0", - "resolve": "^1.19.0" - }, - "engines": { - "node": ">=10", - "npm": ">=6" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/browserslist": { - "version": "4.23.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.2.tgz", - "integrity": "sha512-qkqSyistMYdxAcw+CzbZwlBy8AGmS/eEWs+sEV5TnLRGDOL+C5M2EnH6tlZyg0YoAxGJAFKh61En9BR941GnHA==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "caniuse-lite": "^1.0.30001640", - "electron-to-chromium": "^1.4.820", - "node-releases": "^2.0.14", - "update-browserslist-db": "^1.1.0" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001642", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001642.tgz", - "integrity": "sha512-3XQ0DoRgLijXJErLSl+bLnJ+Et4KqV1PY6JJBGAFlsNsz31zeAIncyeZfLCabHK/jtSh+671RM9YMldxjUPZtA==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/chalk/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/chart.js": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.3.tgz", - "integrity": "sha512-qK1gkGSRYcJzqrrzdR6a+I0vQ4/R+SoODXyAjscQ/4mzuNzySaMCd+hyVxitSY1+L2fjPD1Gbn+ibNqRmwQeLw==", - "license": "MIT", - "dependencies": { - "@kurkle/color": "^0.3.0" - }, - "engines": { - "pnpm": ">=8" - } - }, - "node_modules/clsx": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", - "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "license": "MIT" - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "license": "MIT" - }, - "node_modules/cosmiconfig": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", - "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", - "license": "MIT", - "dependencies": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.2.1", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "license": "MIT" - }, - "node_modules/d3-array": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", - "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", - "license": "ISC", - "dependencies": { - "internmap": "1 - 2" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-color": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", - "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-delaunay": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", - "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", - "license": "ISC", - "dependencies": { - "delaunator": "5" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-ease": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", - "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-format": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", - "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-interpolate": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", - "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", - "license": "ISC", - "dependencies": { - "d3-color": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-path": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", - "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-scale": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", - "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", - "license": "ISC", - "dependencies": { - "d3-array": "2.10.0 - 3", - "d3-format": "1 - 3", - "d3-interpolate": "1.2.0 - 3", - "d3-time": "2.1.1 - 3", - "d3-time-format": "2 - 4" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-shape": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", - "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", - "license": "ISC", - "dependencies": { - "d3-path": "^3.1.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-time": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", - "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", - "license": "ISC", - "dependencies": { - "d3-array": "2 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-time-format": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", - "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", - "license": "ISC", - "dependencies": { - "d3-time": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-timer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", - "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/data-view-buffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", - "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.6", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/data-view-byte-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", - "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/data-view-byte-offset": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", - "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.6", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/dayjs": { - "version": "1.11.11", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.11.tgz", - "integrity": "sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg==", - "license": "MIT" - }, - "node_modules/debug": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", - "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", - "license": "MIT", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decimal.js-light": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", - "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", - "license": "MIT" - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/define-properties": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/delaunator": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", - "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", - "license": "ISC", - "dependencies": { - "robust-predicates": "^3.0.2" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/dom-helpers": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", - "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.8.7", - "csstype": "^3.0.2" - } - }, - "node_modules/dot-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", - "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", - "license": "MIT", - "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.4.829", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.829.tgz", - "integrity": "sha512-5qp1N2POAfW0u1qGAxXEtz6P7bO1m6gpZr5hdf5ve6lxpLM7MpiM4jIPz7xcrNlClQMafbyUDDWjlIQZ1Mw0Rw==", - "license": "ISC" - }, - "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/es-abstract": { - "version": "1.23.3", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", - "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-buffer-byte-length": "^1.0.1", - "arraybuffer.prototype.slice": "^1.0.3", - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", - "data-view-buffer": "^1.0.1", - "data-view-byte-length": "^1.0.1", - "data-view-byte-offset": "^1.0.0", - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "es-set-tostringtag": "^2.0.3", - "es-to-primitive": "^1.2.1", - "function.prototype.name": "^1.1.6", - "get-intrinsic": "^1.2.4", - "get-symbol-description": "^1.0.2", - "globalthis": "^1.0.3", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2", - "has-proto": "^1.0.3", - "has-symbols": "^1.0.3", - "hasown": "^2.0.2", - "internal-slot": "^1.0.7", - "is-array-buffer": "^3.0.4", - "is-callable": "^1.2.7", - "is-data-view": "^1.0.1", - "is-negative-zero": "^2.0.3", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.3", - "is-string": "^1.0.7", - "is-typed-array": "^1.1.13", - "is-weakref": "^1.0.2", - "object-inspect": "^1.13.1", - "object-keys": "^1.1.1", - "object.assign": "^4.1.5", - "regexp.prototype.flags": "^1.5.2", - "safe-array-concat": "^1.1.2", - "safe-regex-test": "^1.0.3", - "string.prototype.trim": "^1.2.9", - "string.prototype.trimend": "^1.0.8", - "string.prototype.trimstart": "^1.0.8", - "typed-array-buffer": "^1.0.2", - "typed-array-byte-length": "^1.0.1", - "typed-array-byte-offset": "^1.0.2", - "typed-array-length": "^1.0.6", - "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.15" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.2.4" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-iterator-helpers": { - "version": "1.0.19", - "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.19.tgz", - "integrity": "sha512-zoMwbCcH5hwUkKJkT8kDIBZSz9I6mVG//+lDCinLCGov4+r7NIy0ld8o03M0cJxl2spVf6ESYVS6/gpIfq1FFw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.3", - "es-errors": "^1.3.0", - "es-set-tostringtag": "^2.0.3", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "globalthis": "^1.0.3", - "has-property-descriptors": "^1.0.2", - "has-proto": "^1.0.3", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.7", - "iterator.prototype": "^1.1.2", - "safe-array-concat": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", - "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", - "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.2.4", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-shim-unscopables": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", - "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.0" - } - }, - "node_modules/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, - "license": "MIT", - "dependencies": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" - } - }, - "node_modules/escalade": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", - "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", - "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.0", - "@humanwhocodes/config-array": "^0.11.14", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-plugin-react": { - "version": "7.34.4", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.34.4.tgz", - "integrity": "sha512-Np+jo9bUwJNxCsT12pXtrGhJgT3T44T1sHhn1Ssr42XFn8TES0267wPGo5nNrMHi8qkyimDAX2BUmkf9pSaVzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-includes": "^3.1.8", - "array.prototype.findlast": "^1.2.5", - "array.prototype.flatmap": "^1.3.2", - "array.prototype.toreversed": "^1.1.2", - "array.prototype.tosorted": "^1.1.4", - "doctrine": "^2.1.0", - "es-iterator-helpers": "^1.0.19", - "estraverse": "^5.3.0", - "hasown": "^2.0.2", - "jsx-ast-utils": "^2.4.1 || ^3.0.0", - "minimatch": "^3.1.2", - "object.entries": "^1.1.8", - "object.fromentries": "^2.0.8", - "object.values": "^1.2.0", - "prop-types": "^15.8.1", - "resolve": "^2.0.0-next.5", - "semver": "^6.3.1", - "string.prototype.matchall": "^4.0.11", - "string.prototype.repeat": "^1.0.0" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" - } - }, - "node_modules/eslint-plugin-react-hooks": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz", - "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" - } - }, - "node_modules/eslint-plugin-react-refresh": { - "version": "0.4.8", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.8.tgz", - "integrity": "sha512-MIKAclwaDFIiYtVBLzDdm16E+Ty4GwhB6wZlCAG1R3Ur+F9Qbo6PRxpA5DK7XtDgm+WlCoAY2WxAwqhmIDHg6Q==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "eslint": ">=7" - } - }, - "node_modules/eslint-plugin-react/node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eslint-plugin-react/node_modules/resolve": { - "version": "2.0.0-next.5", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", - "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/eslint/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/eslint/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/eslint/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/eslint/node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/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, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "license": "MIT" - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "license": "MIT" - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-equals": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.0.1.tgz", - "integrity": "sha512-WF1Wi8PwwSY7/6Kx0vKXtw8RwuSGoM1bvDaJbu7MxDlR1vovZjIAKrnzyrThgAjm6JDTu0fVgWXDlMGspodfoQ==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fastq": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", - "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", - "dev": true, - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "license": "MIT", - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/find-root": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", - "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", - "license": "MIT" - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", - "dev": true, - "license": "MIT", - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flatted": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", - "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", - "dev": true, - "license": "ISC" - }, - "node_modules/follow-redirects": { - "version": "1.15.6", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", - "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "license": "MIT", - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/for-each": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", - "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-callable": "^1.1.3" - } - }, - "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, - "license": "ISC" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/function.prototype.name": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", - "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "functions-have-names": "^1.2.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-symbol-description": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", - "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.5", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/globalthis": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", - "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-properties": "^1.2.1", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true, - "license": "MIT" - }, - "node_modules/has-bigints": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", - "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/hoist-non-react-statics": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", - "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "license": "BSD-3-Clause", - "dependencies": { - "react-is": "^16.7.0" - } - }, - "node_modules/hoist-non-react-statics/node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "license": "MIT" - }, - "node_modules/ignore": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", - "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/immer": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz", - "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/immer" - } - }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/internal-slot": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", - "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "hasown": "^2.0.0", - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/internmap": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", - "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/is-array-buffer": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", - "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "license": "MIT" - }, - "node_modules/is-async-function": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz", - "integrity": "sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-bigints": "^1.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-core-module": { - "version": "2.15.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.0.tgz", - "integrity": "sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA==", - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-data-view": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", - "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-finalizationregistry": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz", - "integrity": "sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-generator-function": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", - "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-map": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", - "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-negative-zero": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", - "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-number-object": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", - "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-set": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", - "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", - "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-typed-array": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", - "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "which-typed-array": "^1.1.14" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakmap": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", - "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakref": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", - "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakset": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.3.tgz", - "integrity": "sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "get-intrinsic": "^1.2.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true, - "license": "MIT" - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/iterator.prototype": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.2.tgz", - "integrity": "sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-properties": "^1.2.1", - "get-intrinsic": "^1.2.1", - "has-symbols": "^1.0.3", - "reflect.getprototypeof": "^1.0.4", - "set-function-name": "^2.0.1" - } - }, - "node_modules/joi": { - "version": "17.13.1", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.1.tgz", - "integrity": "sha512-vaBlIKCyo4FCUtCm7Eu4QZd/q02bWcxfUO6YSXAZOWF6gzcLBeba8kwotUdYJjDLW8Cz8RywsSOqiNJZW0mNvg==", - "license": "BSD-3-Clause", - "dependencies": { - "@hapi/hoek": "^9.3.0", - "@hapi/topo": "^5.1.0", - "@sideway/address": "^4.1.5", - "@sideway/formula": "^3.0.1", - "@sideway/pinpoint": "^2.0.0" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "license": "MIT" - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "license": "MIT" - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/jsx-ast-utils": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", - "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-includes": "^3.1.6", - "array.prototype.flat": "^1.3.1", - "object.assign": "^4.1.4", - "object.values": "^1.1.6" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/jwt-decode": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", - "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "license": "MIT" - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "license": "MIT" - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "license": "MIT", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, - "node_modules/lower-case": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", - "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", - "license": "MIT", - "dependencies": { - "tslib": "^2.0.3" - } - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, - "license": "MIT" - }, - "node_modules/no-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", - "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", - "license": "MIT", - "dependencies": { - "lower-case": "^2.0.2", - "tslib": "^2.0.3" - } - }, - "node_modules/node-releases": { - "version": "2.0.17", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.17.tgz", - "integrity": "sha512-Ww6ZlOiEQfPfXM45v17oabk77Z7mg5bOt7AjDyzy7RjK9OrLrLC8dyZQoAPEOtFX9SaNf1Tdvr5gRJWdTJj7GA==", - "license": "MIT" - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", - "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.assign": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", - "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.5", - "define-properties": "^1.2.1", - "has-symbols": "^1.0.3", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.entries": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.8.tgz", - "integrity": "sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.fromentries": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", - "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.values": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", - "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "license": "MIT" - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/picocolors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", - "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/possible-typed-array-names": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", - "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/postcss": { - "version": "8.4.47", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", - "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.1.0", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prettier": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", - "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", - "dev": true, - "license": "MIT", - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/prop-types": { - "version": "15.8.1", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", - "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, - "node_modules/prop-types/node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "license": "MIT" - }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "license": "MIT" - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/react": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", - "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-dom": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", - "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.2" - }, - "peerDependencies": { - "react": "^18.3.1" - } - }, - "node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "license": "MIT" - }, - "node_modules/react-redux": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.1.2.tgz", - "integrity": "sha512-0OA4dhM1W48l3uzmv6B7TXPCGmokUU4p1M44DGN2/D9a1FjVPukVjER1PcPX97jIg6aUeLq1XJo1IpfbgULn0w==", - "license": "MIT", - "dependencies": { - "@types/use-sync-external-store": "^0.0.3", - "use-sync-external-store": "^1.0.0" - }, - "peerDependencies": { - "@types/react": "^18.2.25", - "react": "^18.0", - "redux": "^5.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "redux": { - "optional": true - } - } - }, - "node_modules/react-refresh": { - "version": "0.14.2", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", - "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-router": { - "version": "6.25.1", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.25.1.tgz", - "integrity": "sha512-u8ELFr5Z6g02nUtpPAggP73Jigj1mRePSwhS/2nkTrlPU5yEkH1vYzWNyvSnSzeeE2DNqWdH+P8OhIh9wuXhTw==", - "license": "MIT", - "dependencies": { - "@remix-run/router": "1.18.0" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "react": ">=16.8" - } - }, - "node_modules/react-router-dom": { - "version": "6.25.1", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.25.1.tgz", - "integrity": "sha512-0tUDpbFvk35iv+N89dWNrJp+afLgd+y4VtorJZuOCXK0kkCWjEvb3vTJM++SYvMEpbVwXKf3FjeVveVEb6JpDQ==", - "license": "MIT", - "dependencies": { - "@remix-run/router": "1.18.0", - "react-router": "6.25.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "react": ">=16.8", - "react-dom": ">=16.8" - } - }, - "node_modules/react-smooth": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.1.tgz", - "integrity": "sha512-OE4hm7XqR0jNOq3Qmk9mFLyd6p2+j6bvbPJ7qlB7+oo0eNcL2l7WQzG6MBnT3EXY6xzkLMUBec3AfewJdA0J8w==", - "license": "MIT", - "dependencies": { - "fast-equals": "^5.0.1", - "prop-types": "^15.8.1", - "react-transition-group": "^4.4.5" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/react-toastify": { - "version": "10.0.5", - "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-10.0.5.tgz", - "integrity": "sha512-mNKt2jBXJg4O7pSdbNUfDdTsK9FIdikfsIE/yUCxbAEXl4HMyJaivrVFcn3Elvt5xvCQYhUZm+hqTIu1UXM3Pw==", - "license": "MIT", - "dependencies": { - "clsx": "^2.1.0" - }, - "peerDependencies": { - "react": ">=18", - "react-dom": ">=18" - } - }, - "node_modules/react-transition-group": { - "version": "4.4.5", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", - "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", - "license": "BSD-3-Clause", - "dependencies": { - "@babel/runtime": "^7.5.5", - "dom-helpers": "^5.0.1", - "loose-envify": "^1.4.0", - "prop-types": "^15.6.2" - }, - "peerDependencies": { - "react": ">=16.6.0", - "react-dom": ">=16.6.0" - } - }, - "node_modules/recharts": { - "version": "2.13.0-alpha.4", - "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.13.0-alpha.4.tgz", - "integrity": "sha512-K9naL6F7pEcDYJE6yFQASSCQecSLPP0JagnvQ9hPtA/aHgsxsnIOjouLP5yrFZehxzfCkV5TEORr7/uNtSr7Qw==", - "license": "MIT", - "dependencies": { - "clsx": "^2.0.0", - "eventemitter3": "^4.0.1", - "lodash": "^4.17.21", - "react-is": "^18.3.1", - "react-smooth": "^4.0.0", - "recharts-scale": "^0.4.4", - "tiny-invariant": "^1.3.1", - "victory-vendor": "^36.6.8" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "react": "^16.0.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/recharts-scale": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz", - "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==", - "license": "MIT", - "dependencies": { - "decimal.js-light": "^2.4.1" - } - }, - "node_modules/redux": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", - "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", - "license": "MIT" - }, - "node_modules/redux-persist": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/redux-persist/-/redux-persist-6.0.0.tgz", - "integrity": "sha512-71LLMbUq2r02ng2We9S215LtPu3fY0KgaGE0k8WRgl6RkqxtGfl7HUozz1Dftwsb0D/5mZ8dwAaPbtnzfvbEwQ==", - "license": "MIT", - "peerDependencies": { - "redux": ">4.0.0" - } - }, - "node_modules/redux-thunk": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", - "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", - "license": "MIT", - "peerDependencies": { - "redux": "^5.0.0" - } - }, - "node_modules/reflect.getprototypeof": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz", - "integrity": "sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.1", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "globalthis": "^1.0.3", - "which-builtin-type": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/regenerator-runtime": { - "version": "0.14.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", - "license": "MIT" - }, - "node_modules/regexp.prototype.flags": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", - "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.6", - "define-properties": "^1.2.1", - "es-errors": "^1.3.0", - "set-function-name": "^2.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/reselect": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz", - "integrity": "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==", - "license": "MIT" - }, - "node_modules/resolve": { - "version": "1.22.8", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", - "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", - "license": "MIT", - "dependencies": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/robust-predicates": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", - "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==", - "license": "Unlicense" - }, - "node_modules/rollup": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.22.4.tgz", - "integrity": "sha512-vD8HJ5raRcWOyymsR6Z3o6+RzfEPCnVLMFJ6vRslO1jt4LO6dUo5Qnpg7y4RkZFM2DMe3WUirkI5c16onjrc6A==", - "license": "MIT", - "dependencies": { - "@types/estree": "1.0.5" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.22.4", - "@rollup/rollup-android-arm64": "4.22.4", - "@rollup/rollup-darwin-arm64": "4.22.4", - "@rollup/rollup-darwin-x64": "4.22.4", - "@rollup/rollup-linux-arm-gnueabihf": "4.22.4", - "@rollup/rollup-linux-arm-musleabihf": "4.22.4", - "@rollup/rollup-linux-arm64-gnu": "4.22.4", - "@rollup/rollup-linux-arm64-musl": "4.22.4", - "@rollup/rollup-linux-powerpc64le-gnu": "4.22.4", - "@rollup/rollup-linux-riscv64-gnu": "4.22.4", - "@rollup/rollup-linux-s390x-gnu": "4.22.4", - "@rollup/rollup-linux-x64-gnu": "4.22.4", - "@rollup/rollup-linux-x64-musl": "4.22.4", - "@rollup/rollup-win32-arm64-msvc": "4.22.4", - "@rollup/rollup-win32-ia32-msvc": "4.22.4", - "@rollup/rollup-win32-x64-msvc": "4.22.4", - "fsevents": "~2.3.2" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/safe-array-concat": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", - "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "get-intrinsic": "^1.2.4", - "has-symbols": "^1.0.3", - "isarray": "^2.0.5" - }, - "engines": { - "node": ">=0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safe-regex-test": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", - "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.6", - "es-errors": "^1.3.0", - "is-regex": "^1.1.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/scheduler": { - "version": "0.23.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", - "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - } - }, - "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/set-function-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", - "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "functions-have-names": "^1.2.3", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/side-channel": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", - "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/snake-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", - "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", - "license": "MIT", - "dependencies": { - "dot-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/string.prototype.matchall": { - "version": "4.0.11", - "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz", - "integrity": "sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.7", - "regexp.prototype.flags": "^1.5.2", - "set-function-name": "^2.0.2", - "side-channel": "^1.0.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.repeat": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", - "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" - } - }, - "node_modules/string.prototype.trim": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", - "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.0", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimend": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", - "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", - "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/stylis": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", - "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", - "license": "MIT" - }, - "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/svg-parser": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", - "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==", - "license": "MIT" - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true, - "license": "MIT" - }, - "node_modules/tiny-invariant": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", - "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", - "license": "MIT" - }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/tslib": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", - "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", - "license": "0BSD" - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typed-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", - "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "es-errors": "^1.3.0", - "is-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/typed-array-byte-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", - "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-proto": "^1.0.3", - "is-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typed-array-byte-offset": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", - "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", - "dev": true, - "license": "MIT", - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-proto": "^1.0.3", - "is-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typed-array-length": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", - "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-proto": "^1.0.3", - "is-typed-array": "^1.1.13", - "possible-typed-array-names": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/unbox-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", - "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "has-bigints": "^1.0.2", - "has-symbols": "^1.0.3", - "which-boxed-primitive": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", - "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.1.2", - "picocolors": "^1.0.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/use-sync-external-store": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz", - "integrity": "sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==", - "license": "MIT", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/victory-vendor": { - "version": "36.9.2", - "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz", - "integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==", - "license": "MIT AND ISC", - "dependencies": { - "@types/d3-array": "^3.0.3", - "@types/d3-ease": "^3.0.0", - "@types/d3-interpolate": "^3.0.1", - "@types/d3-scale": "^4.0.2", - "@types/d3-shape": "^3.1.0", - "@types/d3-time": "^3.0.0", - "@types/d3-timer": "^3.0.0", - "d3-array": "^3.1.6", - "d3-ease": "^3.0.1", - "d3-interpolate": "^3.0.1", - "d3-scale": "^4.0.2", - "d3-shape": "^3.1.0", - "d3-time": "^3.0.0", - "d3-timer": "^3.0.1" - } - }, - "node_modules/vite": { - "version": "5.4.8", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.8.tgz", - "integrity": "sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==", - "license": "MIT", - "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - } - } - }, - "node_modules/vite-plugin-svgr": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/vite-plugin-svgr/-/vite-plugin-svgr-4.2.0.tgz", - "integrity": "sha512-SC7+FfVtNQk7So0XMjrrtLAbEC8qjFPifyD7+fs/E6aaNdVde6umlVVh0QuwDLdOMu7vp5RiGFsB70nj5yo0XA==", - "license": "MIT", - "dependencies": { - "@rollup/pluginutils": "^5.0.5", - "@svgr/core": "^8.1.0", - "@svgr/plugin-jsx": "^8.1.0" - }, - "peerDependencies": { - "vite": "^2.6.0 || 3 || 4 || 5" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-builtin-type": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.1.3.tgz", - "integrity": "sha512-YmjsSMDBYsM1CaFiayOVT06+KJeXf0o5M/CAd4o1lTadFAtacTUM49zoYxr/oroopFDfhvN6iEcBxUyc3gvKmw==", - "dev": true, - "license": "MIT", - "dependencies": { - "function.prototype.name": "^1.1.5", - "has-tostringtag": "^1.0.0", - "is-async-function": "^2.0.0", - "is-date-object": "^1.0.5", - "is-finalizationregistry": "^1.0.2", - "is-generator-function": "^1.0.10", - "is-regex": "^1.1.4", - "is-weakref": "^1.0.2", - "isarray": "^2.0.5", - "which-boxed-primitive": "^1.0.2", - "which-collection": "^1.0.1", - "which-typed-array": "^1.1.9" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-collection": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", - "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-map": "^2.0.3", - "is-set": "^2.0.3", - "is-weakmap": "^2.0.2", - "is-weakset": "^2.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-typed-array": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", - "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", - "dev": true, - "license": "MIT", - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "license": "ISC" - }, - "node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "license": "ISC", - "engines": { - "node": ">= 6" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - } + "name": "client", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "client", + "version": "0.0.0", + "dependencies": { + "@emotion/react": "^11.11.4", + "@emotion/styled": "^11.11.5", + "@fontsource/roboto": "^5.0.13", + "@mui/icons-material": "^5.15.17", + "@mui/lab": "^5.0.0-alpha.170", + "@mui/material": "^5.15.16", + "@mui/x-charts": "^7.5.1", + "@mui/x-data-grid": "7.3.2", + "@mui/x-date-pickers": "7.3.2", + "@reduxjs/toolkit": "2.2.5", + "axios": "^1.7.4", + "chart.js": "^4.4.3", + "dayjs": "1.11.11", + "joi": "17.13.1", + "jwt-decode": "^4.0.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-redux": "9.1.2", + "react-router": "^6.23.0", + "react-router-dom": "^6.23.1", + "react-toastify": "^10.0.5", + "recharts": "2.13.0-alpha.4", + "redux-persist": "6.0.0", + "vite-plugin-svgr": "^4.2.0" + }, + "devDependencies": { + "@types/react": "^18.2.66", + "@types/react-dom": "^18.2.22", + "@vitejs/plugin-react": "^4.2.1", + "eslint": "^8.57.0", + "eslint-plugin-react": "^7.34.1", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.6", + "prettier": "^3.3.3", + "vite": "^5.2.0" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.25.7.tgz", + "integrity": "sha512-0xZJFNE5XMpENsgfHYTw8FbX4kv53mFLn2i3XPoq69LyhYSCBJtitaHx9QnsVTrsogI4Z3+HtEfZ2/GFPOtf5g==", + "license": "MIT", + "dependencies": { + "@babel/highlight": "^7.25.7", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.8.tgz", + "integrity": "sha512-ZsysZyXY4Tlx+Q53XdnOFmqwfB9QDTHYxaZYajWRoBLuLEAwI2UIbtxOjWh/cFaa9IKUlcB+DDuoskLuKu56JA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.8.tgz", + "integrity": "sha512-Oixnb+DzmRT30qu9d3tJSQkxuygWm32DFykT4bRoORPa9hZ/L4KhVB/XiRm6KG+roIEM7DBQlmg27kw2HZkdZg==", + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.25.7", + "@babel/generator": "^7.25.7", + "@babel/helper-compilation-targets": "^7.25.7", + "@babel/helper-module-transforms": "^7.25.7", + "@babel/helpers": "^7.25.7", + "@babel/parser": "^7.25.8", + "@babel/template": "^7.25.7", + "@babel/traverse": "^7.25.7", + "@babel/types": "^7.25.8", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "license": "MIT" + }, + "node_modules/@babel/generator": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.7.tgz", + "integrity": "sha512-5Dqpl5fyV9pIAD62yK9P7fcA768uVPUyrQmqpqstHWgMma4feF1x/oFysBCVZLY5wJ2GkMUCdsNDnGZrPoR6rA==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.25.7", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.7.tgz", + "integrity": "sha512-DniTEax0sv6isaw6qSQSfV4gVRNtw2rte8HHM45t9ZR0xILaufBRNkpMifCRiAPyvL4ACD6v0gfCwCmtOQaV4A==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.25.7", + "@babel/helper-validator-option": "^7.25.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.7.tgz", + "integrity": "sha512-o0xCgpNmRohmnoWKQ0Ij8IdddjyBFE4T2kagL/x6M3+4zUgc+4qTOUBoNe4XxDskt1HPKO007ZPiMgLDq2s7Kw==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.7", + "@babel/types": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.7.tgz", + "integrity": "sha512-k/6f8dKG3yDz/qCwSM+RKovjMix563SLxQFo0UhRNo239SP6n9u5/eLtKD6EAjwta2JHJ49CsD8pms2HdNiMMQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.25.7", + "@babel/helper-simple-access": "^7.25.7", + "@babel/helper-validator-identifier": "^7.25.7", + "@babel/traverse": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz", + "integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.25.7.tgz", + "integrity": "sha512-FPGAkJmyoChQeM+ruBGIDyrT2tKfZJO8NcxdC+CWNJi7N8/rZpSxK7yvBJ5O/nF1gfu5KzN7VKG3YVSLFfRSxQ==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.7", + "@babel/types": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.7.tgz", + "integrity": "sha512-CbkjYdsJNHFk8uqpEkpCvRs3YRp9tY6FmFY7wLMSYuGYkrdUi7r2lc4/wqsvlHoMznX3WJ9IP8giGPq68T/Y6g==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.7.tgz", + "integrity": "sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.7.tgz", + "integrity": "sha512-ytbPLsm+GjArDYXJ8Ydr1c/KJuutjF2besPNbIZnZ6MKUxi/uTA22t2ymmA4WFjZFpjiAMO0xuuJPqK2nvDVfQ==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.7.tgz", + "integrity": "sha512-Sv6pASx7Esm38KQpF/U/OXLwPPrdGHNKoeblRxgZRLXnAtnkEe4ptJPDtAZM7fBLadbc1Q07kQpSiGQ0Jg6tRA==", + "license": "MIT", + "dependencies": { + "@babel/template": "^7.25.7", + "@babel/types": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.25.7.tgz", + "integrity": "sha512-iYyACpW3iW8Fw+ZybQK+drQre+ns/tKpXbNESfrhNnPLIklLbXr7MYJ6gPEd0iETGLOK+SxMjVvKb/ffmk+FEw==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.25.7", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.8.tgz", + "integrity": "sha512-HcttkxzdPucv3nNFmfOOMfFf64KgdJVqm1KaCm25dPGMLElo9nsLvXeJECQg8UzPuBGLyTSA0ZzqCtDSzKTEoQ==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.25.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.24.7.tgz", + "integrity": "sha512-fOPQYbGSgH0HUp4UJO4sMBFjY6DuWq+2i8rixyUMb3CdGixs/gccURvYOAhajBdKDoGajFr3mUq5rH3phtkGzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.24.7.tgz", + "integrity": "sha512-J2z+MWzZHVOemyLweMqngXrgGC42jQ//R0KdxqkIz/OrbVIIlhFI3WigZ5fO+nwFvBlncr4MGapd8vTyc7RPNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.8.tgz", + "integrity": "sha512-5F7SDGs1T72ZczbRwbGO9lQi0NLjQxzl6i4lJxLxfW9U5UluCSyEJeniWvnhl3/euNiqQVbo8zruhsDfid0esA==", + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.7.tgz", + "integrity": "sha512-wRwtAgI3bAS+JGU2upWNL9lSlDcRCqD05BZ1n3X2ONLH1WilFP6O1otQjeMK/1g0pvYcXC7b/qVUB1keofjtZA==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.25.7", + "@babel/parser": "^7.25.7", + "@babel/types": "^7.25.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.7.tgz", + "integrity": "sha512-jatJPT1Zjqvh/1FyJs6qAHL+Dzb7sTb+xr7Q+gM1b+1oBsMsQQ4FkVKb6dFlJvLlVssqkRzV05Jzervt9yhnzg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.25.7", + "@babel/generator": "^7.25.7", + "@babel/parser": "^7.25.7", + "@babel/template": "^7.25.7", + "@babel/types": "^7.25.7", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.25.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.8.tgz", + "integrity": "sha512-JWtuCu8VQsMladxVz/P4HzHUGCAwpuqacmowgXFs5XjxIgKuNjnLokQzuVjlTvIzODaDmpjT3oxcC48vyk9EWg==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.25.7", + "@babel/helper-validator-identifier": "^7.25.7", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emotion/babel-plugin": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz", + "integrity": "sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.1", + "@emotion/memoize": "^0.8.1", + "@emotion/serialize": "^1.1.2", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/cache": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.11.0.tgz", + "integrity": "sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.8.1", + "@emotion/sheet": "^1.2.2", + "@emotion/utils": "^1.2.1", + "@emotion/weak-memoize": "^0.3.1", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.1.tgz", + "integrity": "sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==", + "license": "MIT" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz", + "integrity": "sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.8.1" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==", + "license": "MIT" + }, + "node_modules/@emotion/react": { + "version": "11.11.4", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.11.4.tgz", + "integrity": "sha512-t8AjMlF0gHpvvxk5mAtCqR4vmxiGHCeJBaQO6gncUSdklELOgtwjerNY2yuJNfwnc6vi16U/+uMF+afIawJ9iw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.11.0", + "@emotion/cache": "^11.11.0", + "@emotion/serialize": "^1.1.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1", + "@emotion/utils": "^1.2.1", + "@emotion/weak-memoize": "^0.3.1", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.4.tgz", + "integrity": "sha512-RIN04MBT8g+FnDwgvIUi8czvr1LU1alUMI05LekWB5DGyTm8cCBMCRpq3GqaiyEDRptEXOyXnvZ58GZYu4kBxQ==", + "license": "MIT", + "dependencies": { + "@emotion/hash": "^0.9.1", + "@emotion/memoize": "^0.8.1", + "@emotion/unitless": "^0.8.1", + "@emotion/utils": "^1.2.1", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.2.tgz", + "integrity": "sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==", + "license": "MIT" + }, + "node_modules/@emotion/styled": { + "version": "11.11.5", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.11.5.tgz", + "integrity": "sha512-/ZjjnaNKvuMPxcIiUkf/9SHoG4Q196DRl1w82hQ3WCsjo1IUR8uaGWrC6a87CrYAW0Kb/pK7hk8BnLgLRi9KoQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.11.0", + "@emotion/is-prop-valid": "^1.2.2", + "@emotion/serialize": "^1.1.4", + "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1", + "@emotion/utils": "^1.2.1" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0-rc.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/unitless": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", + "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==", + "license": "MIT" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz", + "integrity": "sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.1.tgz", + "integrity": "sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==", + "license": "MIT" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz", + "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==", + "license": "MIT" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz", + "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.4.tgz", + "integrity": "sha512-a4IowK4QkXl4SCWTGUR0INAfEOX3wtsYw3rKK5InQEHMGObkR8Xk44qYQD9P4r6HHw0iIfK6GUKECmY8sTkqRA==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.4" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.7.tgz", + "integrity": "sha512-wmVfPG5o2xnKDU4jx/m4w5qva9FWHcnZ8BvzEe90D/RpwsJaTAVYPEPdQ8sbr/N8zZTAHlZUTQdqg8ZUbzHmng==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.6.0", + "@floating-ui/utils": "^0.2.4" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.1.tgz", + "integrity": "sha512-4h84MJt3CHrtG18mGsXuLCHMrug49d7DFkU0RMIyshRveBeyV2hmV/pDaF2Uxtu8kgq5r46llp5E5FQiR0K2Yg==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.4.tgz", + "integrity": "sha512-dWO2pw8hhi+WrXq1YJy2yCuWoL20PddgGaqTgVe4cOS9Q6qklXCiA1tJEqX6BEwRNSCP84/afac9hd4MS+zEUA==", + "license": "MIT" + }, + "node_modules/@fontsource/roboto": { + "version": "5.0.13", + "resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-5.0.13.tgz", + "integrity": "sha512-j61DHjsdUCKMXSdNLTOxcG701FWnF0jcqNNQi2iPCDxU8seN/sMxeh62dC++UiagCWq9ghTypX+Pcy7kX+QOeQ==", + "license": "Apache-2.0" + }, + "node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@kurkle/color": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz", + "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==", + "license": "MIT" + }, + "node_modules/@mui/base": { + "version": "5.0.0-beta.40", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.40.tgz", + "integrity": "sha512-I/lGHztkCzvwlXpjD2+SNmvNQvB4227xBXhISPjEaJUXGImOQ9f3D2Yj/T3KasSI/h0MLWy74X0J6clhPmsRbQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@floating-ui/react-dom": "^2.0.8", + "@mui/types": "^7.2.14", + "@mui/utils": "^5.15.14", + "@popperjs/core": "^2.11.8", + "clsx": "^2.1.0", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/core-downloads-tracker": { + "version": "5.16.4", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.16.4.tgz", + "integrity": "sha512-rNdHXhclwjEZnK+//3SR43YRx0VtjdHnUFhMSGYmAMJve+KiwEja/41EYh8V3pZKqF2geKyfcFUenTfDTYUR4w==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + } + }, + "node_modules/@mui/icons-material": { + "version": "5.16.4", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.16.4.tgz", + "integrity": "sha512-j9/CWctv6TH6Dou2uR2EH7UOgu79CW/YcozxCYVLJ7l03pCsiOlJ5sBArnWJxJ+nGkFwyL/1d1k8JEPMDR125A==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.9" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@mui/material": "^5.0.0", + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/lab": { + "version": "5.0.0-alpha.172", + "resolved": "https://registry.npmjs.org/@mui/lab/-/lab-5.0.0-alpha.172.tgz", + "integrity": "sha512-stpa3WTsDE1HamFR4eeS6Bhxalm+u9FhzzNph/PrDMdWSRBHlJs2mqvZ6FEoO22O7MOCwNMqbXTkvEwsyEf0ew==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@mui/base": "5.0.0-beta.40", + "@mui/system": "^5.16.1", + "@mui/types": "^7.2.15", + "@mui/utils": "^5.16.1", + "clsx": "^2.1.0", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@mui/material": ">=5.15.0", + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material": { + "version": "5.16.4", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.16.4.tgz", + "integrity": "sha512-dBnh3/zRYgEVIS3OE4oTbujse3gifA0qLMmuUk13ywsDCbngJsdgwW5LuYeiT5pfA8PGPGSqM7mxNytYXgiMCw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@mui/core-downloads-tracker": "^5.16.4", + "@mui/system": "^5.16.4", + "@mui/types": "^7.2.15", + "@mui/utils": "^5.16.4", + "@popperjs/core": "^2.11.8", + "@types/react-transition-group": "^4.4.10", + "clsx": "^2.1.0", + "csstype": "^3.1.3", + "prop-types": "^15.8.1", + "react-is": "^18.3.1", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/private-theming": { + "version": "5.16.4", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.16.4.tgz", + "integrity": "sha512-ZsAm8cq31SJ37SVWLRlu02v9SRthxnfQofaiv14L5Bht51B0dz6yQEoVU/V8UduZDCCIrWkBHuReVfKhE/UuXA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@mui/utils": "^5.16.4", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/styled-engine": { + "version": "5.16.4", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.16.4.tgz", + "integrity": "sha512-0+mnkf+UiAmTVB8PZFqOhqf729Yh0Cxq29/5cA3VAyDVTRIUUQ8FXQhiAhUIbijFmM72rY80ahFPXIm4WDbzcA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@emotion/cache": "^11.11.0", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.4.1", + "@emotion/styled": "^11.3.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/system": { + "version": "5.16.4", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.16.4.tgz", + "integrity": "sha512-ET1Ujl2/8hbsD611/mqUuNArMCGv/fIWO/f8B3ZqF5iyPHM2aS74vhTNyjytncc4i6dYwGxNk+tLa7GwjNS0/w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@mui/private-theming": "^5.16.4", + "@mui/styled-engine": "^5.16.4", + "@mui/types": "^7.2.15", + "@mui/utils": "^5.16.4", + "clsx": "^2.1.0", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/types": { + "version": "7.2.15", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.15.tgz", + "integrity": "sha512-nbo7yPhtKJkdf9kcVOF8JZHPZTmqXjJ/tI0bdWgHg5tp9AnIN4Y7f7wm9T+0SyGYJk76+GYZ8Q5XaTYAsUHN0Q==", + "license": "MIT", + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils": { + "version": "5.16.4", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.16.4.tgz", + "integrity": "sha512-nlppYwq10TBIFqp7qxY0SvbACOXeOjeVL3pOcDsK0FT8XjrEXh9/+lkg8AEIzD16z7YfiJDQjaJG2OLkE7BxNg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@types/prop-types": "^15.7.12", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^18.3.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/x-charts": { + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/@mui/x-charts/-/x-charts-7.10.0.tgz", + "integrity": "sha512-k5dGcc2IIVXWbWs+mWLPqngTg960UkvUOvBne3724hy4PzuIZpCi5kICB0Lb2uMJ9xKZwAzzbjcbYKa4F7+/NA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.7", + "@mui/base": "^5.0.0-beta.40", + "@mui/system": "^5.16.0", + "@mui/utils": "^5.16.0", + "@react-spring/rafz": "^9.7.3", + "@react-spring/web": "^9.7.3", + "clsx": "^2.1.1", + "d3-color": "^3.1.0", + "d3-delaunay": "^6.0.4", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.2.0", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "@emotion/react": "^11.9.0", + "@emotion/styled": "^11.8.1", + "@mui/material": "^5.15.14", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/x-data-grid": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-7.3.2.tgz", + "integrity": "sha512-seuRiZ2yyhzeUa7Thzap0xvvizGPSEwJRNOjY9kffjUr+0iXGF3PZGEsMoJ7jCjZ2peHX7FjfqBdssDvizxIDQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.0", + "@mui/system": "^5.15.14", + "@mui/utils": "^5.15.14", + "clsx": "^2.1.0", + "prop-types": "^15.8.1", + "reselect": "^4.1.8" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@mui/material": "^5.15.14", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + } + }, + "node_modules/@mui/x-date-pickers": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-7.3.2.tgz", + "integrity": "sha512-i7JaDs1eXSZWyJihfszUHVV0t/C2HvtdMv5tHwv3E3enMx5Hup1vkJ64vZAH2fgGrTHQH8mjxvVsmI6jhDXIUg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.0", + "@mui/base": "^5.0.0-beta.40", + "@mui/system": "^5.15.14", + "@mui/utils": "^5.15.14", + "@types/react-transition-group": "^4.4.10", + "clsx": "^2.1.0", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.9.0", + "@emotion/styled": "^11.8.1", + "@mui/material": "^5.15.14", + "date-fns": "^2.25.0 || ^3.2.0", + "date-fns-jalali": "^2.13.0-0", + "dayjs": "^1.10.7", + "luxon": "^3.0.2", + "moment": "^2.29.4", + "moment-hijri": "^2.1.2", + "moment-jalaali": "^0.7.4 || ^0.8.0 || ^0.9.0 || ^0.10.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "date-fns": { + "optional": true + }, + "date-fns-jalali": { + "optional": true + }, + "dayjs": { + "optional": true + }, + "luxon": { + "optional": true + }, + "moment": { + "optional": true + }, + "moment-hijri": { + "optional": true + }, + "moment-jalaali": { + "optional": true + } + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@react-spring/animated": { + "version": "9.7.3", + "resolved": "https://registry.npmjs.org/@react-spring/animated/-/animated-9.7.3.tgz", + "integrity": "sha512-5CWeNJt9pNgyvuSzQH+uy2pvTg8Y4/OisoscZIR8/ZNLIOI+CatFBhGZpDGTF/OzdNFsAoGk3wiUYTwoJ0YIvw==", + "license": "MIT", + "dependencies": { + "@react-spring/shared": "~9.7.3", + "@react-spring/types": "~9.7.3" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@react-spring/core": { + "version": "9.7.3", + "resolved": "https://registry.npmjs.org/@react-spring/core/-/core-9.7.3.tgz", + "integrity": "sha512-IqFdPVf3ZOC1Cx7+M0cXf4odNLxDC+n7IN3MDcVCTIOSBfqEcBebSv+vlY5AhM0zw05PDbjKrNmBpzv/AqpjnQ==", + "license": "MIT", + "dependencies": { + "@react-spring/animated": "~9.7.3", + "@react-spring/shared": "~9.7.3", + "@react-spring/types": "~9.7.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-spring/donate" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@react-spring/rafz": { + "version": "9.7.3", + "resolved": "https://registry.npmjs.org/@react-spring/rafz/-/rafz-9.7.3.tgz", + "integrity": "sha512-9vzW1zJPcC4nS3aCV+GgcsK/WLaB520Iyvm55ARHfM5AuyBqycjvh1wbmWmgCyJuX4VPoWigzemq1CaaeRSHhQ==", + "license": "MIT" + }, + "node_modules/@react-spring/shared": { + "version": "9.7.3", + "resolved": "https://registry.npmjs.org/@react-spring/shared/-/shared-9.7.3.tgz", + "integrity": "sha512-NEopD+9S5xYyQ0pGtioacLhL2luflh6HACSSDUZOwLHoxA5eku1UPuqcJqjwSD6luKjjLfiLOspxo43FUHKKSA==", + "license": "MIT", + "dependencies": { + "@react-spring/types": "~9.7.3" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@react-spring/types": { + "version": "9.7.3", + "resolved": "https://registry.npmjs.org/@react-spring/types/-/types-9.7.3.tgz", + "integrity": "sha512-Kpx/fQ/ZFX31OtlqVEFfgaD1ACzul4NksrvIgYfIFq9JpDHFwQkMVZ10tbo0FU/grje4rcL4EIrjekl3kYwgWw==", + "license": "MIT" + }, + "node_modules/@react-spring/web": { + "version": "9.7.3", + "resolved": "https://registry.npmjs.org/@react-spring/web/-/web-9.7.3.tgz", + "integrity": "sha512-BXt6BpS9aJL/QdVqEIX9YoUy8CE6TJrU0mNCqSoxdXlIeNcEBWOfIyE6B14ENNsyQKS3wOWkiJfco0tCr/9tUg==", + "license": "MIT", + "dependencies": { + "@react-spring/animated": "~9.7.3", + "@react-spring/core": "~9.7.3", + "@react-spring/shared": "~9.7.3", + "@react-spring/types": "~9.7.3" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@reduxjs/toolkit": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.2.5.tgz", + "integrity": "sha512-aeFA/s5NCG7NoJe/MhmwREJxRkDs0ZaSqt0MxhWUrwCf1UQXpwR87RROJEql0uAkLI6U7snBOYOcKw83ew3FPg==", + "license": "MIT", + "dependencies": { + "immer": "^10.0.3", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.1.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, + "node_modules/@reduxjs/toolkit/node_modules/reselect": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", + "license": "MIT" + }, + "node_modules/@remix-run/router": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.18.0.tgz", + "integrity": "sha512-L3jkqmqoSVBVKHfpGZmLrex0lxR5SucGA0sUfFzGctehw+S/ggL9L/0NnC5mw6P8HUWpFZ3nQw3cRApjjWx9Sw==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz", + "integrity": "sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.4.tgz", + "integrity": "sha512-Fxamp4aEZnfPOcGA8KSNEohV8hX7zVHOemC8jVBoBUHu5zpJK/Eu3uJwt6BMgy9fkvzxDaurgj96F/NiLukF2w==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.22.4.tgz", + "integrity": "sha512-VXoK5UMrgECLYaMuGuVTOx5kcuap1Jm8g/M83RnCHBKOqvPPmROFJGQaZhGccnsFtfXQ3XYa4/jMCJvZnbJBdA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.22.4.tgz", + "integrity": "sha512-xMM9ORBqu81jyMKCDP+SZDhnX2QEVQzTcC6G18KlTQEzWK8r/oNZtKuZaCcHhnsa6fEeOBionoyl5JsAbE/36Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.22.4.tgz", + "integrity": "sha512-aJJyYKQwbHuhTUrjWjxEvGnNNBCnmpHDvrb8JFDbeSH3m2XdHcxDd3jthAzvmoI8w/kSjd2y0udT+4okADsZIw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.22.4.tgz", + "integrity": "sha512-j63YtCIRAzbO+gC2L9dWXRh5BFetsv0j0va0Wi9epXDgU/XUi5dJKo4USTttVyK7fGw2nPWK0PbAvyliz50SCQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.22.4.tgz", + "integrity": "sha512-dJnWUgwWBX1YBRsuKKMOlXCzh2Wu1mlHzv20TpqEsfdZLb3WoJW2kIEsGwLkroYf24IrPAvOT/ZQ2OYMV6vlrg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.22.4.tgz", + "integrity": "sha512-AdPRoNi3NKVLolCN/Sp4F4N1d98c4SBnHMKoLuiG6RXgoZ4sllseuGioszumnPGmPM2O7qaAX/IJdeDU8f26Aw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.22.4.tgz", + "integrity": "sha512-Gl0AxBtDg8uoAn5CCqQDMqAx22Wx22pjDOjBdmG0VIWX3qUBHzYmOKh8KXHL4UpogfJ14G4wk16EQogF+v8hmA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.22.4.tgz", + "integrity": "sha512-3aVCK9xfWW1oGQpTsYJJPF6bfpWfhbRnhdlyhak2ZiyFLDaayz0EP5j9V1RVLAAxlmWKTDfS9wyRyY3hvhPoOg==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.22.4.tgz", + "integrity": "sha512-ePYIir6VYnhgv2C5Xe9u+ico4t8sZWXschR6fMgoPUK31yQu7hTEJb7bCqivHECwIClJfKgE7zYsh1qTP3WHUA==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.22.4.tgz", + "integrity": "sha512-GqFJ9wLlbB9daxhVlrTe61vJtEY99/xB3C8e4ULVsVfflcpmR6c8UZXjtkMA6FhNONhj2eA5Tk9uAVw5orEs4Q==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.22.4.tgz", + "integrity": "sha512-87v0ol2sH9GE3cLQLNEy0K/R0pz1nvg76o8M5nhMR0+Q+BBGLnb35P0fVz4CQxHYXaAOhE8HhlkaZfsdUOlHwg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.22.4.tgz", + "integrity": "sha512-UV6FZMUgePDZrFjrNGIWzDo/vABebuXBhJEqrHxrGiU6HikPy0Z3LfdtciIttEUQfuDdCn8fqh7wiFJjCNwO+g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.22.4.tgz", + "integrity": "sha512-BjI+NVVEGAXjGWYHz/vv0pBqfGoUH0IGZ0cICTn7kB9PyjrATSkX+8WkguNjWoj2qSr1im/+tTGRaY+4/PdcQw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.22.4.tgz", + "integrity": "sha512-SiWG/1TuUdPvYmzmYnmd3IEifzR61Tragkbx9D3+R8mzQqDBz8v+BvZNDlkiTtI9T15KYZhP0ehn3Dld4n9J5g==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.22.4.tgz", + "integrity": "sha512-j8pPKp53/lq9lMXN57S8cFz0MynJk8OWNuUnXct/9KCpKU7DgU3bYMJhwWmcqC0UU29p8Lr0/7KEVcaM6bf47Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sideway/address": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", + "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@sideway/formula": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", + "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", + "license": "BSD-3-Clause" + }, + "node_modules/@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@svgr/babel-plugin-add-jsx-attribute": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", + "integrity": "sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-remove-jsx-attribute": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz", + "integrity": "sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-remove-jsx-empty-expression": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz", + "integrity": "sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-replace-jsx-attribute-value": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz", + "integrity": "sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-svg-dynamic-title": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz", + "integrity": "sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-svg-em-dimensions": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz", + "integrity": "sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-transform-react-native-svg": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz", + "integrity": "sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-transform-svg-component": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz", + "integrity": "sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-preset": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-8.1.0.tgz", + "integrity": "sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==", + "license": "MIT", + "dependencies": { + "@svgr/babel-plugin-add-jsx-attribute": "8.0.0", + "@svgr/babel-plugin-remove-jsx-attribute": "8.0.0", + "@svgr/babel-plugin-remove-jsx-empty-expression": "8.0.0", + "@svgr/babel-plugin-replace-jsx-attribute-value": "8.0.0", + "@svgr/babel-plugin-svg-dynamic-title": "8.0.0", + "@svgr/babel-plugin-svg-em-dimensions": "8.0.0", + "@svgr/babel-plugin-transform-react-native-svg": "8.1.0", + "@svgr/babel-plugin-transform-svg-component": "8.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/core": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz", + "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.21.3", + "@svgr/babel-preset": "8.1.0", + "camelcase": "^6.2.0", + "cosmiconfig": "^8.1.3", + "snake-case": "^3.0.4" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/core/node_modules/cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "license": "MIT", + "dependencies": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@svgr/hast-util-to-babel-ast": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz", + "integrity": "sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.21.3", + "entities": "^4.4.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/plugin-jsx": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz", + "integrity": "sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.21.3", + "@svgr/babel-preset": "8.1.0", + "@svgr/hast-util-to-babel-ast": "8.0.0", + "svg-parser": "^2.0.4" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@svgr/core": "*" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", + "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz", + "integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-P2dlU/q51fkOc/Gfl3Ul9kicV7l+ra934qBFXCFhrZMOL6du1TM0pm1ThYvENukyOn5h9v+yMJ9Fn5JK4QozrQ==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.8.tgz", + "integrity": "sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-shape": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.6.tgz", + "integrity": "sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.3.tgz", + "integrity": "sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "license": "MIT" + }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.12", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", + "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==", + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.3", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz", + "integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==", + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.0", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz", + "integrity": "sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/react-transition-group": { + "version": "4.4.10", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.10.tgz", + "integrity": "sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==", + "license": "MIT", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", + "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==", + "license": "MIT" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.3.tgz", + "integrity": "sha512-NooDe9GpHGqNns1i8XDERg0Vsg5SSYRhRxxyTGogUdkdNt47jal+fbuYi+Yfq6pzRCKXyoPcWisfxE6RIM3GKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.25.2", + "@babel/plugin-transform-react-jsx-self": "^7.24.7", + "@babel/plugin-transform-react-jsx-source": "^7.24.7", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.14.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0" + } + }, + "node_modules/acorn": { + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/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==", + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", + "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", + "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", + "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.toreversed": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/array.prototype.toreversed/-/array.prototype.toreversed-1.1.2.tgz", + "integrity": "sha512-wwDCoT4Ck4Cz7sLtgUmzR5UV3YF5mFHUlbChCzZBQZ+0m2cl/DH3tKgvphv1nKgFsJ48oCSg6p91q2Vm0I/ZMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", + "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", + "is-shared-array-buffer": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axios": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz", + "integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/browserslist": { + "version": "4.24.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", + "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001669", + "electron-to-chromium": "^1.5.41", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.1" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001669", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001669.tgz", + "integrity": "sha512-DlWzFDJqstqtIVx1zeSpIMLjunf5SmwOw0N2Ck/QSQdS8PLS4+9HrLaYei4w8BIAL7IB/UEDu889d8vhCTPA0w==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/chart.js": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.3.tgz", + "integrity": "sha512-qK1gkGSRYcJzqrrzdR6a+I0vQ4/R+SoODXyAjscQ/4mzuNzySaMCd+hyVxitSY1+L2fjPD1Gbn+ibNqRmwQeLw==", + "license": "MIT", + "dependencies": { + "@kurkle/color": "^0.3.0" + }, + "engines": { + "pnpm": ">=8" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "license": "MIT" + }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "license": "MIT", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "license": "ISC", + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/data-view-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", + "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", + "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", + "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/dayjs": { + "version": "1.11.11", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.11.tgz", + "integrity": "sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", + "license": "MIT" + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delaunator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", + "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", + "license": "ISC", + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.41", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.41.tgz", + "integrity": "sha512-dfdv/2xNjX0P8Vzme4cfzHqnPm5xsZXwsolTYr0eyW18IUmNyG08vL+fttvinTfhKfIKdRoqkDIC9e9iWQCNYQ==", + "license": "ISC" + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.23.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", + "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "data-view-buffer": "^1.0.1", + "data-view-byte-length": "^1.0.1", + "data-view-byte-offset": "^1.0.0", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.0.3", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "hasown": "^2.0.2", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.1", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.3", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.13", + "is-weakref": "^1.0.2", + "object-inspect": "^1.13.1", + "object-keys": "^1.1.1", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.2", + "safe-array-concat": "^1.1.2", + "safe-regex-test": "^1.0.3", + "string.prototype.trim": "^1.2.9", + "string.prototype.trimend": "^1.0.8", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.2", + "typed-array-byte-length": "^1.0.1", + "typed-array-byte-offset": "^1.0.2", + "typed-array-length": "^1.0.6", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.15" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.0.19", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.19.tgz", + "integrity": "sha512-zoMwbCcH5hwUkKJkT8kDIBZSz9I6mVG//+lDCinLCGov4+r7NIy0ld8o03M0cJxl2spVf6ESYVS6/gpIfq1FFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.3", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "globalthis": "^1.0.3", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.7", + "iterator.prototype": "^1.1.2", + "safe-array-concat": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", + "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", + "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.0" + } + }, + "node_modules/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, + "license": "MIT", + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.34.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.34.4.tgz", + "integrity": "sha512-Np+jo9bUwJNxCsT12pXtrGhJgT3T44T1sHhn1Ssr42XFn8TES0267wPGo5nNrMHi8qkyimDAX2BUmkf9pSaVzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.2", + "array.prototype.toreversed": "^1.1.2", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.0.19", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.8", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.0", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.11", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz", + "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.13", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.13.tgz", + "integrity": "sha512-f1EppwrpJRWmqDTyvAyomFVDYRtrS7iTEqv3nokETnMiMzs2SSTmKRTACce4O2p4jYyowiSMvpdwC/RLcMFhuQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": ">=7" + } + }, + "node_modules/eslint-plugin-react/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/eslint/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/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, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-equals": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.0.1.tgz", + "integrity": "sha512-WF1Wi8PwwSY7/6Kx0vKXtw8RwuSGoM1bvDaJbu7MxDlR1vovZjIAKrnzyrThgAjm6JDTu0fVgWXDlMGspodfoQ==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "license": "MIT" + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true, + "license": "ISC" + }, + "node_modules/follow-redirects": { + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", + "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", + "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/ignore": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/immer": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz", + "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/internal-slot": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.0", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", + "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "license": "MIT" + }, + "node_modules/is-async-function": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz", + "integrity": "sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.0.tgz", + "integrity": "sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", + "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz", + "integrity": "sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", + "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.3.tgz", + "integrity": "sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/iterator.prototype": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.2.tgz", + "integrity": "sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "get-intrinsic": "^1.2.1", + "has-symbols": "^1.0.3", + "reflect.getprototypeof": "^1.0.4", + "set-function-name": "^2.0.1" + } + }, + "node_modules/joi": { + "version": "17.13.1", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.1.tgz", + "integrity": "sha512-vaBlIKCyo4FCUtCm7Eu4QZd/q02bWcxfUO6YSXAZOWF6gzcLBeba8kwotUdYJjDLW8Cz8RywsSOqiNJZW0mNvg==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.3.0", + "@hapi/topo": "^5.1.0", + "@sideway/address": "^4.1.5", + "@sideway/formula": "^3.0.1", + "@sideway/pinpoint": "^2.0.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/jwt-decode": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "license": "MIT", + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node_modules/node-releases": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "license": "MIT" + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.8.tgz", + "integrity": "sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.values": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", + "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.4.47", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", + "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.1.0", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", + "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" + }, + "node_modules/react-redux": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.1.2.tgz", + "integrity": "sha512-0OA4dhM1W48l3uzmv6B7TXPCGmokUU4p1M44DGN2/D9a1FjVPukVjER1PcPX97jIg6aUeLq1XJo1IpfbgULn0w==", + "license": "MIT", + "dependencies": { + "@types/use-sync-external-store": "^0.0.3", + "use-sync-external-store": "^1.0.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25", + "react": "^18.0", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, + "node_modules/react-refresh": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", + "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "6.25.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.25.1.tgz", + "integrity": "sha512-u8ELFr5Z6g02nUtpPAggP73Jigj1mRePSwhS/2nkTrlPU5yEkH1vYzWNyvSnSzeeE2DNqWdH+P8OhIh9wuXhTw==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.18.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.25.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.25.1.tgz", + "integrity": "sha512-0tUDpbFvk35iv+N89dWNrJp+afLgd+y4VtorJZuOCXK0kkCWjEvb3vTJM++SYvMEpbVwXKf3FjeVveVEb6JpDQ==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.18.0", + "react-router": "6.25.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/react-smooth": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.1.tgz", + "integrity": "sha512-OE4hm7XqR0jNOq3Qmk9mFLyd6p2+j6bvbPJ7qlB7+oo0eNcL2l7WQzG6MBnT3EXY6xzkLMUBec3AfewJdA0J8w==", + "license": "MIT", + "dependencies": { + "fast-equals": "^5.0.1", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/react-toastify": { + "version": "10.0.5", + "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-10.0.5.tgz", + "integrity": "sha512-mNKt2jBXJg4O7pSdbNUfDdTsK9FIdikfsIE/yUCxbAEXl4HMyJaivrVFcn3Elvt5xvCQYhUZm+hqTIu1UXM3Pw==", + "license": "MIT", + "dependencies": { + "clsx": "^2.1.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/recharts": { + "version": "2.13.0-alpha.4", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.13.0-alpha.4.tgz", + "integrity": "sha512-K9naL6F7pEcDYJE6yFQASSCQecSLPP0JagnvQ9hPtA/aHgsxsnIOjouLP5yrFZehxzfCkV5TEORr7/uNtSr7Qw==", + "license": "MIT", + "dependencies": { + "clsx": "^2.0.0", + "eventemitter3": "^4.0.1", + "lodash": "^4.17.21", + "react-is": "^18.3.1", + "react-smooth": "^4.0.0", + "recharts-scale": "^0.4.4", + "tiny-invariant": "^1.3.1", + "victory-vendor": "^36.6.8" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/recharts-scale": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz", + "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==", + "license": "MIT", + "dependencies": { + "decimal.js-light": "^2.4.1" + } + }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", + "license": "MIT" + }, + "node_modules/redux-persist": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/redux-persist/-/redux-persist-6.0.0.tgz", + "integrity": "sha512-71LLMbUq2r02ng2We9S215LtPu3fY0KgaGE0k8WRgl6RkqxtGfl7HUozz1Dftwsb0D/5mZ8dwAaPbtnzfvbEwQ==", + "license": "MIT", + "peerDependencies": { + "redux": ">4.0.0" + } + }, + "node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "license": "MIT", + "peerDependencies": { + "redux": "^5.0.0" + } + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz", + "integrity": "sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.1", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "globalthis": "^1.0.3", + "which-builtin-type": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "license": "MIT" + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", + "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/reselect": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz", + "integrity": "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==", + "license": "MIT" + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/robust-predicates": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", + "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==", + "license": "Unlicense" + }, + "node_modules/rollup": { + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.22.4.tgz", + "integrity": "sha512-vD8HJ5raRcWOyymsR6Z3o6+RzfEPCnVLMFJ6vRslO1jt4LO6dUo5Qnpg7y4RkZFM2DMe3WUirkI5c16onjrc6A==", + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.5" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.22.4", + "@rollup/rollup-android-arm64": "4.22.4", + "@rollup/rollup-darwin-arm64": "4.22.4", + "@rollup/rollup-darwin-x64": "4.22.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.22.4", + "@rollup/rollup-linux-arm-musleabihf": "4.22.4", + "@rollup/rollup-linux-arm64-gnu": "4.22.4", + "@rollup/rollup-linux-arm64-musl": "4.22.4", + "@rollup/rollup-linux-powerpc64le-gnu": "4.22.4", + "@rollup/rollup-linux-riscv64-gnu": "4.22.4", + "@rollup/rollup-linux-s390x-gnu": "4.22.4", + "@rollup/rollup-linux-x64-gnu": "4.22.4", + "@rollup/rollup-linux-x64-musl": "4.22.4", + "@rollup/rollup-win32-arm64-msvc": "4.22.4", + "@rollup/rollup-win32-ia32-msvc": "4.22.4", + "@rollup/rollup-win32-x64-msvc": "4.22.4", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", + "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", + "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-regex": "^1.1.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/snake-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", + "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", + "license": "MIT", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz", + "integrity": "sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.7", + "regexp.prototype.flags": "^1.5.2", + "set-function-name": "^2.0.2", + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", + "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", + "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", + "license": "MIT" + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/svg-parser": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", + "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==", + "license": "MIT" + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT" + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/tslib": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", + "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", + "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", + "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", + "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", + "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz", + "integrity": "sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/victory-vendor": { + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz", + "integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==", + "license": "MIT AND ISC", + "dependencies": { + "@types/d3-array": "^3.0.3", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" + } + }, + "node_modules/vite": { + "version": "5.4.8", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.8.tgz", + "integrity": "sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==", + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-plugin-svgr": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/vite-plugin-svgr/-/vite-plugin-svgr-4.2.0.tgz", + "integrity": "sha512-SC7+FfVtNQk7So0XMjrrtLAbEC8qjFPifyD7+fs/E6aaNdVde6umlVVh0QuwDLdOMu7vp5RiGFsB70nj5yo0XA==", + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.5", + "@svgr/core": "^8.1.0", + "@svgr/plugin-jsx": "^8.1.0" + }, + "peerDependencies": { + "vite": "^2.6.0 || 3 || 4 || 5" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.1.3.tgz", + "integrity": "sha512-YmjsSMDBYsM1CaFiayOVT06+KJeXf0o5M/CAd4o1lTadFAtacTUM49zoYxr/oroopFDfhvN6iEcBxUyc3gvKmw==", + "dev": true, + "license": "MIT", + "dependencies": { + "function.prototype.name": "^1.1.5", + "has-tostringtag": "^1.0.0", + "is-async-function": "^2.0.0", + "is-date-object": "^1.0.5", + "is-finalizationregistry": "^1.0.2", + "is-generator-function": "^1.0.10", + "is-regex": "^1.1.4", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", + "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "license": "ISC" + }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } } diff --git a/Client/package.json b/Client/package.json index 948114e35..7f9b3db2c 100644 --- a/Client/package.json +++ b/Client/package.json @@ -1,49 +1,49 @@ { - "name": "client", - "private": true, - "version": "0.0.0", - "type": "module", - "scripts": { - "dev": "vite", - "build": "vite build", - "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", - "preview": "vite preview" - }, - "dependencies": { - "@emotion/react": "^11.11.4", - "@emotion/styled": "^11.11.5", - "@fontsource/roboto": "^5.0.13", - "@mui/icons-material": "^5.15.17", - "@mui/lab": "^5.0.0-alpha.170", - "@mui/material": "^5.15.16", - "@mui/x-charts": "^7.5.1", - "@mui/x-data-grid": "7.3.2", - "@mui/x-date-pickers": "7.3.2", - "@reduxjs/toolkit": "2.2.5", - "axios": "^1.7.4", - "chart.js": "^4.4.3", - "dayjs": "1.11.11", - "joi": "17.13.1", - "jwt-decode": "^4.0.0", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "react-redux": "9.1.2", - "react-router": "^6.23.0", - "react-router-dom": "^6.23.1", - "react-toastify": "^10.0.5", - "recharts": "2.13.0-alpha.4", - "redux-persist": "6.0.0", - "vite-plugin-svgr": "^4.2.0" - }, - "devDependencies": { - "@types/react": "^18.2.66", - "@types/react-dom": "^18.2.22", - "@vitejs/plugin-react": "^4.2.1", - "eslint": "^8.57.0", - "eslint-plugin-react": "^7.34.1", - "eslint-plugin-react-hooks": "^4.6.0", - "eslint-plugin-react-refresh": "^0.4.6", - "prettier": "^3.3.3", - "vite": "^5.2.0" - } + "name": "client", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", + "preview": "vite preview" + }, + "dependencies": { + "@emotion/react": "^11.11.4", + "@emotion/styled": "^11.11.5", + "@fontsource/roboto": "^5.0.13", + "@mui/icons-material": "^5.15.17", + "@mui/lab": "^5.0.0-alpha.170", + "@mui/material": "^5.15.16", + "@mui/x-charts": "^7.5.1", + "@mui/x-data-grid": "7.3.2", + "@mui/x-date-pickers": "7.3.2", + "@reduxjs/toolkit": "2.2.5", + "axios": "^1.7.4", + "chart.js": "^4.4.3", + "dayjs": "1.11.11", + "joi": "17.13.1", + "jwt-decode": "^4.0.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-redux": "9.1.2", + "react-router": "^6.23.0", + "react-router-dom": "^6.23.1", + "react-toastify": "^10.0.5", + "recharts": "2.13.0-alpha.4", + "redux-persist": "6.0.0", + "vite-plugin-svgr": "^4.2.0" + }, + "devDependencies": { + "@types/react": "^18.2.66", + "@types/react-dom": "^18.2.22", + "@vitejs/plugin-react": "^4.2.1", + "eslint": "^8.57.0", + "eslint-plugin-react": "^7.34.1", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.6", + "prettier": "^3.3.3", + "vite": "^5.2.0" + } } diff --git a/Client/src/App.jsx b/Client/src/App.jsx index 9093b00fc..98772ecca 100644 --- a/Client/src/App.jsx +++ b/Client/src/App.jsx @@ -40,177 +40,177 @@ import { getAppSettings } from "./Features/Settings/settingsSlice"; import { logger } from "./Utils/Logger"; // Import the logger import { networkService } from "./main"; function App() { - const AdminCheckedRegister = withAdminCheck(Register); - const MonitorsWithAdminProp = withAdminProp(Monitors); - const MonitorDetailsWithAdminProp = withAdminProp(Details); - const PageSpeedWithAdminProp = withAdminProp(PageSpeed); - const PageSpeedDetailsWithAdminProp = withAdminProp(PageSpeedDetails); - const MaintenanceWithAdminProp = withAdminProp(Maintenance); - const SettingsWithAdminProp = withAdminProp(Settings); - const AdvancedSettingsWithAdminProp = withAdminProp(AdvancedSettings); - const mode = useSelector((state) => state.ui.mode); - const { authToken } = useSelector((state) => state.auth); - const dispatch = useDispatch(); + const AdminCheckedRegister = withAdminCheck(Register); + const MonitorsWithAdminProp = withAdminProp(Monitors); + const MonitorDetailsWithAdminProp = withAdminProp(Details); + const PageSpeedWithAdminProp = withAdminProp(PageSpeed); + const PageSpeedDetailsWithAdminProp = withAdminProp(PageSpeedDetails); + const MaintenanceWithAdminProp = withAdminProp(Maintenance); + const SettingsWithAdminProp = withAdminProp(Settings); + const AdvancedSettingsWithAdminProp = withAdminProp(AdvancedSettings); + const mode = useSelector((state) => state.ui.mode); + const { authToken } = useSelector((state) => state.auth); + const dispatch = useDispatch(); - useEffect(() => { - if (authToken) { - dispatch(getAppSettings({ authToken })); - } - }, [dispatch, authToken]); + useEffect(() => { + if (authToken) { + dispatch(getAppSettings({ authToken })); + } + }, [dispatch, authToken]); - // Cleanup - useEffect(() => { - return () => { - logger.cleanup(); - networkService.cleanup(); - }; - }, []); + // Cleanup + useEffect(() => { + return () => { + logger.cleanup(); + networkService.cleanup(); + }; + }, []); - return ( - - - - } - > - } - /> - } - /> - } - /> - } - /> - } - /> - } - /> + return ( + + + + } + > + } + /> + } + /> + } + /> + } + /> + } + /> + } + /> - } - /> - } - /> - } - /> - } - /> - } - /> - } - /> - - } - /> - - } - /> - - } - /> - } - /> - } - /> - } - /> - } - /> - + } + /> + } + /> + } + /> + } + /> + } + /> + } + /> + + } + /> + + } + /> + + } + /> + } + /> + } + /> + } + /> + } + /> + - } - /> + } + /> - } - /> - } - /> - {/* } /> */} - } - /> - } - /> - } - /> - } - /> - } - /> - - - - ); + } + /> + } + /> + {/* } /> */} + } + /> + } + /> + } + /> + } + /> + } + /> + + + + ); } export default App; diff --git a/Client/src/Components/Alert/index.css b/Client/src/Components/Alert/index.css index 4a1a11c67..233b5c466 100644 --- a/Client/src/Components/Alert/index.css +++ b/Client/src/Components/Alert/index.css @@ -1,9 +1,9 @@ .alert { - margin: 0; - width: fit-content; + margin: 0; + width: fit-content; } .alert, .alert button, .alert .MuiTypography-root { - font-size: var(--env-var-font-size-medium); + font-size: var(--env-var-font-size-medium); } diff --git a/Client/src/Components/Alert/index.jsx b/Client/src/Components/Alert/index.jsx index cafd58451..49c089813 100644 --- a/Client/src/Components/Alert/index.jsx +++ b/Client/src/Components/Alert/index.jsx @@ -13,9 +13,9 @@ import "./index.css"; */ const icons = { - info: , - error: , - warning: , + info: , + error: , + warning: , }; /** @@ -30,94 +30,92 @@ const icons = { */ const Alert = ({ variant, title, body, isToast, hasIcon = true, onClick }) => { - const theme = useTheme(); - const { text, bg, border } = theme.palette[variant]; - const icon = icons[variant]; + const theme = useTheme(); + const { text, bg, border } = theme.palette[variant]; + const icon = icons[variant]; - return ( - - {hasIcon && {icon}} - - {title && ( - - {title} - - )} - {body && ( - - {body} - - )} - {hasIcon && isToast && ( - - )} - - {isToast && ( - - - - )} - - ); + return ( + + {hasIcon && {icon}} + + {title && ( + {title} + )} + {body && ( + {body} + )} + {hasIcon && isToast && ( + + )} + + {isToast && ( + + + + )} + + ); }; Alert.propTypes = { - variant: PropTypes.oneOf(["info", "error", "warning"]).isRequired, - title: PropTypes.string, - body: PropTypes.string, - isToast: PropTypes.bool, - hasIcon: PropTypes.bool, - onClick: function (props, propName, componentName) { - if (props.isToast && !props[propName]) { - return new Error( - `Prop '${propName}' is required when 'isToast' is true in '${componentName}'.` - ); - } - return null; - }, + variant: PropTypes.oneOf(["info", "error", "warning"]).isRequired, + title: PropTypes.string, + body: PropTypes.string, + isToast: PropTypes.bool, + hasIcon: PropTypes.bool, + onClick: function (props, propName, componentName) { + if (props.isToast && !props[propName]) { + return new Error( + `Prop '${propName}' is required when 'isToast' is true in '${componentName}'.` + ); + } + return null; + }, }; export default Alert; diff --git a/Client/src/Components/Animated/PulseDot.jsx b/Client/src/Components/Animated/PulseDot.jsx index 9ac536fe6..fc38ba7d9 100644 --- a/Client/src/Components/Animated/PulseDot.jsx +++ b/Client/src/Components/Animated/PulseDot.jsx @@ -15,48 +15,48 @@ import { Box, Stack } from "@mui/material"; */ const PulseDot = ({ color }) => { - return ( - - - - ); + return ( + + + + ); }; PulseDot.propTypes = { - color: PropTypes.string.isRequired, + color: PropTypes.string.isRequired, }; export default PulseDot; diff --git a/Client/src/Components/Avatar/index.jsx b/Client/src/Components/Avatar/index.jsx index e1263d3ad..3760c04b8 100644 --- a/Client/src/Components/Avatar/index.jsx +++ b/Client/src/Components/Avatar/index.jsx @@ -9,19 +9,19 @@ import { useEffect, useState } from "react"; * @returns {string} */ const stringToColor = (string) => { - let hash = 0; - let i; - for (i = 0; i < string.length; i += 1) { - hash = string.charCodeAt(i) + ((hash << 5) - hash); - } + let hash = 0; + let i; + for (i = 0; i < string.length; i += 1) { + hash = string.charCodeAt(i) + ((hash << 5) - hash); + } - let color = "#"; - for (i = 0; i < 3; i += 1) { - const value = (hash >> (i * 8)) & 0xff; - color += `00${value.toString(16)}`.slice(-2); - } + let color = "#"; + for (i = 0; i < 3; i += 1) { + const value = (hash >> (i * 8)) & 0xff; + color += `00${value.toString(16)}`.slice(-2); + } - return color; + return color; }; /** @@ -37,54 +37,52 @@ const stringToColor = (string) => { */ const Avatar = ({ src, small, sx }) => { - const { user } = useSelector((state) => state.auth); + const { user } = useSelector((state) => state.auth); - const style = small ? { width: 32, height: 32 } : { width: 64, height: 64 }; - const border = small ? 1 : 3; + const style = small ? { width: 32, height: 32 } : { width: 64, height: 64 }; + const border = small ? 1 : 3; - const [image, setImage] = useState(); - useEffect(() => { - if (user.avatarImage) { - setImage(`data:image/png;base64,${user.avatarImage}`); - } - }, [user?.avatarImage]); + const [image, setImage] = useState(); + useEffect(() => { + if (user.avatarImage) { + setImage(`data:image/png;base64,${user.avatarImage}`); + } + }, [user?.avatarImage]); - return ( - - {user.firstName?.charAt(0)} - {user.lastName?.charAt(0)} - - ); + return ( + + {user.firstName?.charAt(0)} + {user.lastName?.charAt(0)} + + ); }; Avatar.propTypes = { - src: PropTypes.string, - small: PropTypes.bool, - sx: PropTypes.object, + src: PropTypes.string, + small: PropTypes.bool, + sx: PropTypes.object, }; export default Avatar; diff --git a/Client/src/Components/BasicTable/index.css b/Client/src/Components/BasicTable/index.css index 06ea3e3ce..1d4a354e9 100644 --- a/Client/src/Components/BasicTable/index.css +++ b/Client/src/Components/BasicTable/index.css @@ -1,133 +1,130 @@ .MuiTable-root .host { - width: fit-content; - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; + width: fit-content; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; } .MuiTable-root .host span { - font-size: 11px; + font-size: 11px; } .MuiTable-root .label { - line-height: 1; - border-radius: var(--env-var-radius-2); - padding: 7px; - font-size: var(--env-var-font-size-small-plus); + line-height: 1; + border-radius: var(--env-var-radius-2); + padding: 7px; + font-size: var(--env-var-font-size-small-plus); } .MuiPaper-root:has(table.MuiTable-root) { - box-shadow: none; + box-shadow: none; } -.MuiTable-root - .MuiTableBody-root - .MuiTableRow-root:last-child - .MuiTableCell-root { - border: none; +.MuiTable-root .MuiTableBody-root .MuiTableRow-root:last-child .MuiTableCell-root { + border: none; } .MuiTable-root .MuiTableHead-root .MuiTableCell-root, .MuiTable-root .MuiTableBody-root .MuiTableCell-root { - font-size: var(--env-var-font-size-medium); + font-size: var(--env-var-font-size-medium); } .MuiTable-root .MuiTableHead-root .MuiTableCell-root { - padding: var(--env-var-spacing-1) var(--env-var-spacing-2); - font-weight: 500; + padding: var(--env-var-spacing-1) var(--env-var-spacing-2); + font-weight: 500; } .MuiTable-root .MuiTableHead-root span { - display: inline-block; - height: 17px; - width: 20px; - overflow: hidden; - margin-bottom: -3px; - margin-left: 3px; + display: inline-block; + height: 17px; + width: 20px; + overflow: hidden; + margin-bottom: -3px; + margin-left: 3px; } .MuiTable-root .MuiTableHead-root span svg { - width: 20px; - height: 20px; + width: 20px; + height: 20px; } .MuiTable-root .MuiTableBody-root .MuiTableCell-root { - padding: 6px var(--env-var-spacing-2); + padding: 6px var(--env-var-spacing-2); } .MuiTable-root .MuiTableBody-root .MuiTableRow-root { - height: 50px; + height: 50px; } .MuiPaper-root + .MuiPagination-root { - border-radius: var(--env-var-radius-1); - padding: var(--env-var-spacing-1-plus) var(--env-var-spacing-2); + border-radius: var(--env-var-radius-1); + padding: var(--env-var-spacing-1-plus) var(--env-var-spacing-2); } .MuiPaper-root + .MuiPagination-root ul { - justify-content: center; + justify-content: center; } .MuiPaper-root + .MuiPagination-root button { - font-size: var(--env-var-font-size-medium); - font-weight: 500; + font-size: var(--env-var-font-size-medium); + font-weight: 500; } .MuiPaper-root + .MuiPagination-root ul li:first-child { - margin-right: auto; + margin-right: auto; } .MuiPaper-root + .MuiPagination-root ul li:last-child { - margin-left: auto; + margin-left: auto; } .MuiPaper-root + .MuiPagination-root ul li:first-child button { - padding: 0 var(--env-var-spacing-1) 0 var(--env-var-spacing-1-plus); + padding: 0 var(--env-var-spacing-1) 0 var(--env-var-spacing-1-plus); } .MuiPaper-root + .MuiPagination-root ul li:last-child button { - padding: 0 var(--env-var-spacing-1-plus) 0 var(--env-var-spacing-1); + padding: 0 var(--env-var-spacing-1-plus) 0 var(--env-var-spacing-1); } .MuiPaper-root + .MuiPagination-root ul li:first-child button::after, .MuiPaper-root + .MuiPagination-root ul li:last-child button::before { - position: relative; - display: inline-block; + position: relative; + display: inline-block; } .MuiPaper-root + .MuiPagination-root ul li:first-child button::after { - content: "Previous"; - margin-left: 15px; + content: "Previous"; + margin-left: 15px; } .MuiPaper-root + .MuiPagination-root ul li:last-child button::before { - content: "Next"; - margin-right: 15px; + content: "Next"; + margin-right: 15px; } .MuiPaper-root + .MuiPagination-root div.MuiPaginationItem-root { - user-select: none; + user-select: none; } .MuiTablePagination-root p { - font-weight: 500; - font-size: var(--env-var-font-size-small-plus); + font-weight: 500; + font-size: var(--env-var-font-size-small-plus); } .MuiTablePagination-root .MuiTablePagination-select.MuiSelect-select { - text-align: left; - text-align-last: left; + text-align: left; + text-align-last: left; } .MuiTablePagination-root button { - min-width: 0; - padding: 4px; - margin-left: 5px; + min-width: 0; + padding: 4px; + margin-left: 5px; } .MuiTablePagination-root svg { - width: 22px; - height: 22px; + width: 22px; + height: 22px; } .MuiTablePagination-root .MuiSelect-icon { - width: 16px; - height: 16px; - top: 50%; - right: 8%; - transform: translateY(-50%); + width: 16px; + height: 16px; + top: 50%; + right: 8%; + transform: translateY(-50%); } .MuiTablePagination-root button.Mui-disabled { - opacity: 0.4; + opacity: 0.4; } .table-container .MuiTable-root .MuiTableHead-root .MuiTableCell-root { - text-transform: uppercase; - opacity: 0.8; - font-size: var(--env-var-font-size-small-plus); - font-weight: 400; + text-transform: uppercase; + opacity: 0.8; + font-size: var(--env-var-font-size-small-plus); + font-weight: 400; } .monitors .MuiTableCell-root:not(:first-of-type):not(:last-of-type), .monitors .MuiTableCell-root:not(:first-of-type):not(:last-of-type) { - padding-left: var(--env-var-spacing-1); - padding-right: var(--env-var-spacing-1); + padding-left: var(--env-var-spacing-1); + padding-right: var(--env-var-spacing-1); } diff --git a/Client/src/Components/BasicTable/index.jsx b/Client/src/Components/BasicTable/index.jsx index e9fbbadfd..e519a079f 100644 --- a/Client/src/Components/BasicTable/index.jsx +++ b/Client/src/Components/BasicTable/index.jsx @@ -2,18 +2,18 @@ import PropTypes from "prop-types"; import { useState, useEffect } from "react"; import { useTheme } from "@emotion/react"; import { - TableContainer, - Paper, - Table, - TableHead, - TableRow, - TableCell, - TableBody, - TablePagination, - Box, - Typography, - Stack, - Button, + TableContainer, + Paper, + Table, + TableHead, + TableRow, + TableCell, + TableBody, + TablePagination, + Box, + Typography, + Stack, + Button, } from "@mui/material"; import { useDispatch, useSelector } from "react-redux"; import { setRowsPerPage } from "../../Features/UI/uiSlice"; @@ -36,64 +36,64 @@ import "./index.css"; * @returns {JSX.Element} Pagination actions component. */ const TablePaginationActions = (props) => { - const { count, page, rowsPerPage, onPageChange } = props; + const { count, page, rowsPerPage, onPageChange } = props; - const handleFirstPageButtonClick = (event) => { - onPageChange(event, 0); - }; - const handleBackButtonClick = (event) => { - onPageChange(event, page - 1); - }; - const handleNextButtonClick = (event) => { - onPageChange(event, page + 1); - }; - const handleLastPageButtonClick = (event) => { - onPageChange(event, Math.max(0, Math.ceil(count / rowsPerPage) - 1)); - }; + const handleFirstPageButtonClick = (event) => { + onPageChange(event, 0); + }; + const handleBackButtonClick = (event) => { + onPageChange(event, page - 1); + }; + const handleNextButtonClick = (event) => { + onPageChange(event, page + 1); + }; + const handleLastPageButtonClick = (event) => { + onPageChange(event, Math.max(0, Math.ceil(count / rowsPerPage) - 1)); + }; - return ( - - - - - - - ); + return ( + + + + + + + ); }; TablePaginationActions.propTypes = { - count: PropTypes.number.isRequired, - page: PropTypes.number.isRequired, - rowsPerPage: PropTypes.number.isRequired, - onPageChange: PropTypes.func.isRequired, + count: PropTypes.number.isRequired, + page: PropTypes.number.isRequired, + rowsPerPage: PropTypes.number.isRequired, + onPageChange: PropTypes.func.isRequired, }; /** @@ -150,172 +150,173 @@ TablePaginationActions.propTypes = { */ const BasicTable = ({ data, paginated, reversed, table }) => { - const DEFAULT_ROWS_PER_PAGE = 5; - const theme = useTheme(); - const dispatch = useDispatch(); - const uiState = useSelector((state) => state.ui); - let rowsPerPage = uiState?.[table]?.rowsPerPage ?? DEFAULT_ROWS_PER_PAGE; - const [page, setPage] = useState(0); + const DEFAULT_ROWS_PER_PAGE = 5; + const theme = useTheme(); + const dispatch = useDispatch(); + const uiState = useSelector((state) => state.ui); + let rowsPerPage = uiState?.[table]?.rowsPerPage ?? DEFAULT_ROWS_PER_PAGE; + const [page, setPage] = useState(0); - useEffect(() => { - setPage(0); - }, [data]); + useEffect(() => { + setPage(0); + }, [data]); - const handleChangePage = (event, newPage) => { - setPage(newPage); - }; + const handleChangePage = (event, newPage) => { + setPage(newPage); + }; - const handleChangeRowsPerPage = (event) => { - dispatch( - setRowsPerPage({ - value: parseInt(event.target.value, 10), - table: table, - }) - ); - setPage(0); - }; + const handleChangeRowsPerPage = (event) => { + dispatch( + setRowsPerPage({ + value: parseInt(event.target.value, 10), + table: table, + }) + ); + setPage(0); + }; - let displayData = []; + let displayData = []; - if (data && data.rows) { - let rows = reversed ? [...data.rows].reverse() : data.rows; - displayData = paginated - ? rows.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) - : rows; - } + if (data && data.rows) { + let rows = reversed ? [...data.rows].reverse() : data.rows; + displayData = paginated + ? rows.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) + : rows; + } - if (!data || !data.cols || !data.rows) { - return
No data
; - } + if (!data || !data.cols || !data.rows) { + return
No data
; + } - /** - * Helper function to calculate the range of displayed rows. - * @returns {string} - */ - const getRange = () => { - let start = page * rowsPerPage + 1; - let end = Math.min(page * rowsPerPage + rowsPerPage, data.rows.length); - return `${start} - ${end}`; - }; + /** + * Helper function to calculate the range of displayed rows. + * @returns {string} + */ + const getRange = () => { + let start = page * rowsPerPage + 1; + let end = Math.min(page * rowsPerPage + rowsPerPage, data.rows.length); + return `${start} - ${end}`; + }; - return ( - <> - - - - - {data.cols.map((col) => ( - {col.name} - ))} - - - - {displayData.map((row) => { - return ( - - {row.data.map((cell) => { - return {cell.data}; - })} - - ); - })} - -
-
- {paginated && ( - - - Showing {getRange()} of {data.rows.length} monitor(s) - - - `Page ${page + 1} of ${Math.max( - 0, - Math.ceil(count / rowsPerPage) - )}` - } - slotProps={{ - select: { - MenuProps: { - keepMounted: true, - PaperProps: { - className: "pagination-dropdown", - sx: { - mt: 0, - mb: theme.spacing(2), - }, - }, - transformOrigin: { vertical: "bottom", horizontal: "left" }, - anchorOrigin: { vertical: "top", horizontal: "left" }, - sx: { mt: theme.spacing(-2) }, - }, - inputProps: { id: "pagination-dropdown" }, - IconComponent: SelectorVertical, - sx: { - ml: theme.spacing(4), - mr: theme.spacing(12), - minWidth: theme.spacing(20), - textAlign: "left", - "&.Mui-focused > div": { - backgroundColor: theme.palette.background.main, - }, - }, - }, - }} - sx={{ - mt: theme.spacing(6), - color: theme.palette.text.secondary, - "& svg path": { - stroke: theme.palette.text.tertiary, - strokeWidth: 1.3, - }, - "& .MuiSelect-select": { - border: 1, - borderColor: theme.palette.border.light, - borderRadius: theme.shape.borderRadius, - }, - }} - /> - - )} - - ); + return ( + <> + + + + + {data.cols.map((col) => ( + {col.name} + ))} + + + + {displayData.map((row) => { + return ( + + {row.data.map((cell) => { + return {cell.data}; + })} + + ); + })} + +
+
+ {paginated && ( + + + Showing {getRange()} of {data.rows.length} monitor(s) + + + `Page ${page + 1} of ${Math.max(0, Math.ceil(count / rowsPerPage))}` + } + slotProps={{ + select: { + MenuProps: { + keepMounted: true, + PaperProps: { + className: "pagination-dropdown", + sx: { + mt: 0, + mb: theme.spacing(2), + }, + }, + transformOrigin: { vertical: "bottom", horizontal: "left" }, + anchorOrigin: { vertical: "top", horizontal: "left" }, + sx: { mt: theme.spacing(-2) }, + }, + inputProps: { id: "pagination-dropdown" }, + IconComponent: SelectorVertical, + sx: { + ml: theme.spacing(4), + mr: theme.spacing(12), + minWidth: theme.spacing(20), + textAlign: "left", + "&.Mui-focused > div": { + backgroundColor: theme.palette.background.main, + }, + }, + }, + }} + sx={{ + mt: theme.spacing(6), + color: theme.palette.text.secondary, + "& svg path": { + stroke: theme.palette.text.tertiary, + strokeWidth: 1.3, + }, + "& .MuiSelect-select": { + border: 1, + borderColor: theme.palette.border.light, + borderRadius: theme.shape.borderRadius, + }, + }} + /> + + )} + + ); }; BasicTable.propTypes = { - data: PropTypes.object.isRequired, - paginated: PropTypes.bool, - reversed: PropTypes.bool, - rowPerPage: PropTypes.number, - table: PropTypes.string, + data: PropTypes.object.isRequired, + paginated: PropTypes.bool, + reversed: PropTypes.bool, + rowPerPage: PropTypes.number, + table: PropTypes.string, }; export default BasicTable; diff --git a/Client/src/Components/Breadcrumbs/index.css b/Client/src/Components/Breadcrumbs/index.css index a425946e8..b089f8366 100644 --- a/Client/src/Components/Breadcrumbs/index.css +++ b/Client/src/Components/Breadcrumbs/index.css @@ -1,22 +1,22 @@ .MuiBreadcrumbs-root { - height: 34px; + height: 34px; } .MuiBreadcrumbs-root svg { - width: 16px; - height: 16px; + width: 16px; + height: 16px; } .MuiBreadcrumbs-root .MuiBreadcrumbs-li a { - font-size: var(--env-var-font-size-medium); - font-weight: 400; + font-size: var(--env-var-font-size-medium); + font-weight: 400; } .MuiBreadcrumbs-root .MuiBreadcrumbs-li:not(:last-child) { - cursor: pointer; + cursor: pointer; } .MuiBreadcrumbs-root .MuiBreadcrumbs-li:last-child a { - font-weight: 500; - opacity: 1; - cursor: default; + font-weight: 500; + opacity: 1; + cursor: default; } .MuiBreadcrumbs-root .MuiBreadcrumbs-separator { - margin: 0; + margin: 0; } diff --git a/Client/src/Components/Breadcrumbs/index.jsx b/Client/src/Components/Breadcrumbs/index.jsx index f2ca2089f..d2c8e4019 100644 --- a/Client/src/Components/Breadcrumbs/index.jsx +++ b/Client/src/Components/Breadcrumbs/index.jsx @@ -18,59 +18,59 @@ import "./index.css"; */ const Breadcrumbs = ({ list }) => { - const theme = useTheme(); - const navigate = useNavigate(); + const theme = useTheme(); + const navigate = useNavigate(); - return ( - } - aria-label="breadcrumb" - px={theme.spacing(2)} - py={theme.spacing(3.5)} - width="fit-content" - backgroundColor={theme.palette.background.fill} - borderRadius={theme.shape.borderRadius} - lineHeight="18px" - sx={{ - "& .MuiBreadcrumbs-li:not(:last-of-type):hover a": { - backgroundColor: theme.palette.other.fill, - opacity: 1, - }, - }} - > - {list.map((item, index) => { - return ( - navigate(item.path)} - sx={{ - opacity: 0.8, - textTransform: "capitalize", - "&, &:hover": { - color: theme.palette.text.tertiary, - }, - }} - > - {item.name} - - ); - })} - - ); + return ( + } + aria-label="breadcrumb" + px={theme.spacing(2)} + py={theme.spacing(3.5)} + width="fit-content" + backgroundColor={theme.palette.background.fill} + borderRadius={theme.shape.borderRadius} + lineHeight="18px" + sx={{ + "& .MuiBreadcrumbs-li:not(:last-of-type):hover a": { + backgroundColor: theme.palette.other.fill, + opacity: 1, + }, + }} + > + {list.map((item, index) => { + return ( + navigate(item.path)} + sx={{ + opacity: 0.8, + textTransform: "capitalize", + "&, &:hover": { + color: theme.palette.text.tertiary, + }, + }} + > + {item.name} + + ); + })} + + ); }; Breadcrumbs.propTypes = { - list: PropTypes.arrayOf( - PropTypes.shape({ - name: PropTypes.string.isRequired, - path: PropTypes.string.isRequired, - }).isRequired - ).isRequired, + list: PropTypes.arrayOf( + PropTypes.shape({ + name: PropTypes.string.isRequired, + path: PropTypes.string.isRequired, + }).isRequired + ).isRequired, }; export default Breadcrumbs; diff --git a/Client/src/Components/Charts/BarChart/index.jsx b/Client/src/Components/Charts/BarChart/index.jsx index e2d8c7b45..5c58a143a 100644 --- a/Client/src/Components/Charts/BarChart/index.jsx +++ b/Client/src/Components/Charts/BarChart/index.jsx @@ -6,164 +6,166 @@ import "./index.css"; import { useSelector } from "react-redux"; const BarChart = ({ checks = [] }) => { - const theme = useTheme(); - const [animate, setAnimate] = useState(false); - const uiTimezone = useSelector((state) => state.ui.timezone); + const theme = useTheme(); + const [animate, setAnimate] = useState(false); + const uiTimezone = useSelector((state) => state.ui.timezone); - useEffect(() => { - setAnimate(true); - }); + useEffect(() => { + setAnimate(true); + }); - // set responseTime to average if there's only one check - if (checks.length === 1) { - checks[0] = { ...checks[0], responseTime: 50 }; - } + // set responseTime to average if there's only one check + if (checks.length === 1) { + checks[0] = { ...checks[0], responseTime: 50 }; + } - if (checks.length !== 25) { - const placeholders = Array(25 - checks.length).fill("placeholder"); - checks = [...checks, ...placeholders]; - } + if (checks.length !== 25) { + const placeholders = Array(25 - checks.length).fill("placeholder"); + checks = [...checks, ...placeholders]; + } - return ( - event.stopPropagation()} - sx={{ - cursor: "default", - }} - > - {checks.map((check, index) => - check === "placeholder" ? ( - - ) : ( - - - {formatDateWithTz( - check.createdAt, - "ddd, MMMM D, YYYY, HH:mm A", - uiTimezone - )} - - - - - - Response Time - - - {check.originalResponseTime} - - {" "} - ms - - - - - - } - placement="top" - key={`check-${check?._id}`} - slotProps={{ - popper: { - className: "bar-tooltip", - modifiers: [ - { - name: "offset", - options: { - offset: [0, -10], - }, - }, - ], - sx: { - "& .MuiTooltip-tooltip": { - backgroundColor: theme.palette.background.main, - border: 1, - borderColor: theme.palette.border.dark, - borderRadius: theme.shape.borderRadius, - boxShadow: theme.shape.boxShadow, - px: theme.spacing(4), - py: theme.spacing(2), - }, - "& .MuiTooltip-tooltip p": { - fontSize: 12, - color: theme.palette.text.tertiary, - fontWeight: 500, - }, - "& .MuiTooltip-tooltip span": { - fontSize: 11, - color: theme.palette.text.tertiary, - fontWeight: 600, - }, - }, - }, - }} - > - .MuiBox-root": { - filter: "brightness(0.8)", - }, - }} - > - - - - ) - )} - - ); + return ( + event.stopPropagation()} + sx={{ + cursor: "default", + }} + > + {checks.map((check, index) => + check === "placeholder" ? ( + + ) : ( + + + {formatDateWithTz( + check.createdAt, + "ddd, MMMM D, YYYY, HH:mm A", + uiTimezone + )} + + + + + + Response Time + + + {check.originalResponseTime} + + {" "} + ms + + + + + + } + placement="top" + key={`check-${check?._id}`} + slotProps={{ + popper: { + className: "bar-tooltip", + modifiers: [ + { + name: "offset", + options: { + offset: [0, -10], + }, + }, + ], + sx: { + "& .MuiTooltip-tooltip": { + backgroundColor: theme.palette.background.main, + border: 1, + borderColor: theme.palette.border.dark, + borderRadius: theme.shape.borderRadius, + boxShadow: theme.shape.boxShadow, + px: theme.spacing(4), + py: theme.spacing(2), + }, + "& .MuiTooltip-tooltip p": { + fontSize: 12, + color: theme.palette.text.tertiary, + fontWeight: 500, + }, + "& .MuiTooltip-tooltip span": { + fontSize: 11, + color: theme.palette.text.tertiary, + fontWeight: 600, + }, + }, + }, + }} + > + .MuiBox-root": { + filter: "brightness(0.8)", + }, + }} + > + + + + ) + )} + + ); }; export default BarChart; diff --git a/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx b/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx index 30dae4b9a..c45fa1b72 100644 --- a/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx +++ b/Client/src/Components/Charts/MonitorDetailsAreaChart/index.jsx @@ -1,12 +1,12 @@ import PropTypes from "prop-types"; import { - AreaChart, - Area, - XAxis, - Tooltip, - CartesianGrid, - ResponsiveContainer, - Text, + AreaChart, + Area, + XAxis, + Tooltip, + CartesianGrid, + ResponsiveContainer, + Text, } from "recharts"; import { Box, Stack, Typography } from "@mui/material"; import { useTheme } from "@emotion/react"; @@ -16,181 +16,197 @@ import { formatDateWithTz } from "../../../Utils/timeUtils"; import "./index.css"; const CustomToolTip = ({ active, payload, label }) => { - const uiTimezone = useSelector((state) => state.ui.timezone); + const uiTimezone = useSelector((state) => state.ui.timezone); - const theme = useTheme(); - if (active && payload && payload.length) { - return ( - - - {formatDateWithTz(label, "ddd, MMMM D, YYYY, h:mm A", uiTimezone)} - - - - - - Response Time - {" "} - - {payload[0].payload.originalResponseTime} - - {" "} - ms - - - - - {/* Display original value */} - - ); - } - return null; + const theme = useTheme(); + if (active && payload && payload.length) { + return ( + + + {formatDateWithTz(label, "ddd, MMMM D, YYYY, h:mm A", uiTimezone)} + + + + + + Response Time + {" "} + + {payload[0].payload.originalResponseTime} + + {" "} + ms + + + + + {/* Display original value */} + + ); + } + return null; }; const CustomTick = ({ x, y, payload, index }) => { - const theme = useTheme(); + const theme = useTheme(); - const uiTimezone = useSelector((state) => state.ui.timezone); + const uiTimezone = useSelector((state) => state.ui.timezone); - // Render nothing for the first tick - if (index === 0) return null; - return ( - - {formatDateWithTz(payload?.value, "h:mm a", uiTimezone)} - - ); + // Render nothing for the first tick + if (index === 0) return null; + return ( + + {formatDateWithTz(payload?.value, "h:mm a", uiTimezone)} + + ); }; CustomTick.propTypes = { - x: PropTypes.number, - y: PropTypes.number, - payload: PropTypes.object, - index: PropTypes.number, + x: PropTypes.number, + y: PropTypes.number, + payload: PropTypes.object, + index: PropTypes.number, }; const MonitorDetailsAreaChart = ({ checks }) => { - const theme = useTheme(); - const memoizedChecks = useMemo(() => checks, [checks[0]]); - const [isHovered, setIsHovered] = useState(false); + const theme = useTheme(); + const memoizedChecks = useMemo(() => checks, [checks[0]]); + const [isHovered, setIsHovered] = useState(false); - return ( - - setIsHovered(true)} - onMouseLeave={() => setIsHovered(false)} - > - - - - - - - - } - minTickGap={0} - axisLine={false} - tickLine={false} - height={20} - interval="equidistantPreserveStart" - /> - } - wrapperStyle={{ pointerEvents: "none" }} - /> - - - - ); + return ( + + setIsHovered(true)} + onMouseLeave={() => setIsHovered(false)} + > + + + + + + + + } + minTickGap={0} + axisLine={false} + tickLine={false} + height={20} + interval="equidistantPreserveStart" + /> + } + wrapperStyle={{ pointerEvents: "none" }} + /> + + + + ); }; MonitorDetailsAreaChart.propTypes = { - checks: PropTypes.array, + checks: PropTypes.array, }; CustomToolTip.propTypes = { - active: PropTypes.bool, - payload: PropTypes.arrayOf( - PropTypes.shape({ - payload: PropTypes.shape({ - originalResponseTime: PropTypes.number.isRequired, - }).isRequired, - }) - ), - label: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + active: PropTypes.bool, + payload: PropTypes.arrayOf( + PropTypes.shape({ + payload: PropTypes.shape({ + originalResponseTime: PropTypes.number.isRequired, + }).isRequired, + }) + ), + label: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), }; export default MonitorDetailsAreaChart; diff --git a/Client/src/Components/Check/Check.jsx b/Client/src/Components/Check/Check.jsx index 9eaba846e..7ef51137e 100644 --- a/Client/src/Components/Check/Check.jsx +++ b/Client/src/Components/Check/Check.jsx @@ -21,50 +21,49 @@ import { useTheme } from "@emotion/react"; * @returns {React.Element} The `Check` component with a check icon and a label, defined by the `text` prop. */ const Check = ({ text, variant = "info", outlined = false }) => { - const theme = useTheme(); - const colors = { - success: theme.palette.success.main, - error: theme.palette.error.text, - info: theme.palette.info.border, - }; + const theme = useTheme(); + const colors = { + success: theme.palette.success.main, + error: theme.palette.error.text, + info: theme.palette.info.border, + }; - return ( - - {outlined ? ( - - ) : ( - path": { fill: colors[variant] }, - }} - > - - - )} - - {text} - - - ); + return ( + + {outlined ? ( + + ) : ( + path": { fill: colors[variant] }, + }} + > + + + )} + + {text} + + + ); }; Check.propTypes = { - text: PropTypes.oneOfType([PropTypes.string, PropTypes.element]).isRequired, - variant: PropTypes.oneOf(["info", "error", "success"]), - outlined: PropTypes.bool, + text: PropTypes.oneOfType([PropTypes.string, PropTypes.element]).isRequired, + variant: PropTypes.oneOf(["info", "error", "success"]), + outlined: PropTypes.bool, }; export default Check; diff --git a/Client/src/Components/Check/check.css b/Client/src/Components/Check/check.css index 8b1378917..e69de29bb 100644 --- a/Client/src/Components/Check/check.css +++ b/Client/src/Components/Check/check.css @@ -1 +0,0 @@ - diff --git a/Client/src/Components/Dialog/index.jsx b/Client/src/Components/Dialog/index.jsx index 389d3a6e8..55ec801d7 100644 --- a/Client/src/Components/Dialog/index.jsx +++ b/Client/src/Components/Dialog/index.jsx @@ -1,80 +1,103 @@ import LoadingButton from "@mui/lab/LoadingButton"; import { Button, Modal, Stack, Typography } from "@mui/material"; import PropTypes from "prop-types"; -const Dialog = ({ modelTitle, modelDescription, - open, onClose, title, confirmationBtnLbl, confirmationBtnOnClick, cancelBtnLbl, cancelBtnOnClick, theme, isLoading, description }) => { - return - - - {title} - - {description && - {description} - } - - - - {confirmationBtnLbl} - - - - -} +const Dialog = ({ + modelTitle, + modelDescription, + open, + onClose, + title, + confirmationBtnLbl, + confirmationBtnOnClick, + cancelBtnLbl, + cancelBtnOnClick, + theme, + isLoading, + description, +}) => { + return ( + + + + {title} + + {description && ( + + {description} + + )} + + + + {confirmationBtnLbl} + + + + + ); +}; Dialog.propTypes = { - modelTitle: PropTypes.string.isRequired, - modelDescription: PropTypes.string.isRequired, - open: PropTypes.bool.isRequired, - onClose: PropTypes.func.isRequired, - title: PropTypes.string.isRequired, - confirmationBtnLbl: PropTypes.string.isRequired, - confirmationBtnOnClick: PropTypes.func.isRequired, - cancelBtnLbl: PropTypes.string.isRequired, - cancelBtnOnClick: PropTypes.func.isRequired, - theme: PropTypes.object.isRequired, - isLoading: PropTypes.bool.isRequired, - description: PropTypes.string -} + modelTitle: PropTypes.string.isRequired, + modelDescription: PropTypes.string.isRequired, + open: PropTypes.bool.isRequired, + onClose: PropTypes.func.isRequired, + title: PropTypes.string.isRequired, + confirmationBtnLbl: PropTypes.string.isRequired, + confirmationBtnOnClick: PropTypes.func.isRequired, + cancelBtnLbl: PropTypes.string.isRequired, + cancelBtnOnClick: PropTypes.func.isRequired, + theme: PropTypes.object.isRequired, + isLoading: PropTypes.bool.isRequired, + description: PropTypes.string, +}; -export default Dialog \ No newline at end of file +export default Dialog; diff --git a/Client/src/Components/Fallback/index.css b/Client/src/Components/Fallback/index.css index 293eb13b3..f00fc1b09 100644 --- a/Client/src/Components/Fallback/index.css +++ b/Client/src/Components/Fallback/index.css @@ -1,24 +1,24 @@ [class*="fallback__"] { - width: fit-content; - margin: auto; - margin-top: 100px; + width: fit-content; + margin: auto; + margin-top: 100px; } [class*="fallback__"] h1.MuiTypography-root { - font-size: var(--env-var-font-size-large); - font-weight: 600; + font-size: var(--env-var-font-size-large); + font-weight: 600; } [class*="fallback__"] button.MuiButtonBase-root, [class*="fallback__"] .check { - width: max-content; + width: max-content; } [class*="fallback__"] button.MuiButtonBase-root { - height: 34px; + height: 34px; } [class*="fallback__"] .check span.MuiTypography-root, [class*="fallback__"] button.MuiButtonBase-root { - font-size: var(--env-var-font-size-medium); + font-size: var(--env-var-font-size-medium); } .fallback__status > .MuiStack-root { - margin-left: var(--env-var-spacing-2); + margin-left: var(--env-var-spacing-2); } diff --git a/Client/src/Components/Fallback/index.jsx b/Client/src/Components/Fallback/index.jsx index 8d6b21f73..55e22cd68 100644 --- a/Client/src/Components/Fallback/index.jsx +++ b/Client/src/Components/Fallback/index.jsx @@ -21,67 +21,71 @@ import "./index.css"; */ const Fallback = ({ title, checks, link = "/", isAdmin }) => { - const theme = useTheme(); - const navigate = useNavigate(); - const mode = useSelector((state) => state.ui.mode); + const theme = useTheme(); + const navigate = useNavigate(); + const mode = useSelector((state) => state.ui.mode); - return ( - - {mode === "light" ? ( - - ) : ( - - )} - - - - - - A {title} is used to: - - {checks.map((check, index) => ( - - ))} - - {/* TODO - display a different fallback if user is not an admin*/} - {isAdmin && ( - - )} - - ); + return ( + + {mode === "light" ? ( + + ) : ( + + )} + + + + + + A {title} is used to: + + {checks.map((check, index) => ( + + ))} + + {/* TODO - display a different fallback if user is not an admin*/} + {isAdmin && ( + + )} + + ); }; Fallback.propTypes = { - title: PropTypes.string.isRequired, - checks: PropTypes.arrayOf(PropTypes.string).isRequired, - link: PropTypes.string, - isAdmin: PropTypes.bool, + title: PropTypes.string.isRequired, + checks: PropTypes.arrayOf(PropTypes.string).isRequired, + link: PropTypes.string, + isAdmin: PropTypes.bool, }; export default Fallback; diff --git a/Client/src/Components/Inputs/Checkbox/index.jsx b/Client/src/Components/Inputs/Checkbox/index.jsx index e7af04a64..7d9a31bd3 100644 --- a/Client/src/Components/Inputs/Checkbox/index.jsx +++ b/Client/src/Components/Inputs/Checkbox/index.jsx @@ -29,68 +29,68 @@ import "./index.css"; */ const Checkbox = ({ - id, - label, - size = "medium", - isChecked, - value, - onChange, - isDisabled, + id, + label, + size = "medium", + isChecked, + value, + onChange, + isDisabled, }) => { - const sizes = { small: "14px", medium: "16px", large: "18px" }; - const theme = useTheme(); + const sizes = { small: "14px", medium: "16px", large: "18px" }; + const theme = useTheme(); - return ( - } - checkedIcon={} - inputProps={{ - "aria-label": "controlled checkbox", - id: id, - }} - sx={{ - "&:hover": { backgroundColor: "transparent" }, - "& svg": { width: sizes[size], height: sizes[size] }, - }} - /> - } - label={label} - disabled={isDisabled} - sx={{ - borderRadius: theme.shape.borderRadius, - p: theme.spacing(2.5), - m: theme.spacing(-2.5), - "& .MuiButtonBase-root": { - width: theme.spacing(10), - p: 0, - mr: theme.spacing(6), - }, - "&:not(:has(.Mui-disabled)):hover": { - backgroundColor: theme.palette.background.accent, - }, - "& span.MuiTypography-root": { - fontSize: 13, - color: theme.palette.text.tertiary, - }, - }} - /> - ); + return ( + } + checkedIcon={} + inputProps={{ + "aria-label": "controlled checkbox", + id: id, + }} + sx={{ + "&:hover": { backgroundColor: "transparent" }, + "& svg": { width: sizes[size], height: sizes[size] }, + }} + /> + } + label={label} + disabled={isDisabled} + sx={{ + borderRadius: theme.shape.borderRadius, + p: theme.spacing(2.5), + m: theme.spacing(-2.5), + "& .MuiButtonBase-root": { + width: theme.spacing(10), + p: 0, + mr: theme.spacing(6), + }, + "&:not(:has(.Mui-disabled)):hover": { + backgroundColor: theme.palette.background.accent, + }, + "& span.MuiTypography-root": { + fontSize: 13, + color: theme.palette.text.tertiary, + }, + }} + /> + ); }; Checkbox.propTypes = { - id: PropTypes.string.isRequired, - label: PropTypes.string.isRequired, - size: PropTypes.oneOf(["small", "medium", "large"]), - isChecked: PropTypes.bool.isRequired, - value: PropTypes.string, - onChange: PropTypes.func, - isDisabled: PropTypes.bool, + id: PropTypes.string.isRequired, + label: PropTypes.string.isRequired, + size: PropTypes.oneOf(["small", "medium", "large"]), + isChecked: PropTypes.bool.isRequired, + value: PropTypes.string, + onChange: PropTypes.func, + isDisabled: PropTypes.bool, }; export default Checkbox; diff --git a/Client/src/Components/Inputs/Field/index.css b/Client/src/Components/Inputs/Field/index.css index ab1812013..2d251e4bf 100644 --- a/Client/src/Components/Inputs/Field/index.css +++ b/Client/src/Components/Inputs/Field/index.css @@ -1,31 +1,31 @@ .field { - min-width: 250px; + min-width: 250px; } .field h3.MuiTypography-root, .field h5.MuiTypography-root, .field input, .field textarea, .field .input-error { - font-size: var(--env-var-font-size-medium); + font-size: var(--env-var-font-size-medium); } .field h5.MuiTypography-root { - position: relative; - opacity: 0.8; - padding-right: var(--env-var-spacing-1-minus); + position: relative; + opacity: 0.8; + padding-right: var(--env-var-spacing-1-minus); } .field .MuiInputBase-root:has(input) { - height: 34px; + height: 34px; } .field .MuiInputBase-root:has(.MuiInputAdornment-root) { - padding-right: var(--env-var-spacing-1-minus); + padding-right: var(--env-var-spacing-1-minus); } .field input { - height: 100%; - padding: 0 var(--env-var-spacing-1-minus); + height: 100%; + padding: 0 var(--env-var-spacing-1-minus); } .field .MuiInputBase-root:has(textarea) { - padding: var(--env-var-spacing-1-minus); + padding: var(--env-var-spacing-1-minus); } .register-page .field .MuiOutlinedInput-root fieldset, @@ -36,5 +36,5 @@ .forgot-password-page .field input, .set-new-password-page .field .MuiOutlinedInput-root fieldset, .set-new-password-page .field input { - border-radius: var(--env-var-radius-2); + border-radius: var(--env-var-radius-2); } diff --git a/Client/src/Components/Inputs/Field/index.jsx b/Client/src/Components/Inputs/Field/index.jsx index 9dbf616a9..1b5ab0c7c 100644 --- a/Client/src/Components/Inputs/Field/index.jsx +++ b/Client/src/Components/Inputs/Field/index.jsx @@ -1,13 +1,7 @@ import PropTypes from "prop-types"; import { forwardRef, useState } from "react"; import { useTheme } from "@emotion/react"; -import { - IconButton, - InputAdornment, - Stack, - TextField, - Typography, -} from "@mui/material"; +import { IconButton, InputAdornment, Stack, TextField, Typography } from "@mui/material"; import VisibilityOff from "@mui/icons-material/VisibilityOff"; import Visibility from "@mui/icons-material/Visibility"; import "./index.css"; @@ -33,208 +27,199 @@ import "./index.css"; */ const Field = forwardRef( - ( - { - type = "text", - id, - name, - label, - https, - isRequired, - isOptional, - optionalLabel, - autoComplete, - placeholder, - value, - onChange, - onInput, - error, - disabled, - hidden, - }, - ref - ) => { - const theme = useTheme(); + ( + { + type = "text", + id, + name, + label, + https, + isRequired, + isOptional, + optionalLabel, + autoComplete, + placeholder, + value, + onChange, + onInput, + error, + disabled, + hidden, + }, + ref + ) => { + const theme = useTheme(); - const [isVisible, setVisible] = useState(false); + const [isVisible, setVisible] = useState(false); - return ( - - ), - endAdornment: type === "password" && ( - - setVisible((show) => !show)} - tabIndex={-1} - sx={{ - color: theme.palette.border.dark, - padding: theme.spacing(1), - "&:focus": { - outline: "none", - }, - "& .MuiTouchRipple-root": { - pointerEvents: "none", - display: "none", - }, - }} - > - {!isVisible ? : } - - - ), - }} - /> - {error && ( - - {error} - - )} - - ); - } + return ( + + ), + endAdornment: type === "password" && ( + + setVisible((show) => !show)} + tabIndex={-1} + sx={{ + color: theme.palette.border.dark, + padding: theme.spacing(1), + "&:focus": { + outline: "none", + }, + "& .MuiTouchRipple-root": { + pointerEvents: "none", + display: "none", + }, + }} + > + {!isVisible ? : } + + + ), + }} + /> + {error && ( + + {error} + + )} + + ); + } ); Field.displayName = "Field"; Field.propTypes = { - type: PropTypes.oneOf([ - "text", - "password", - "url", - "email", - "description", - "number", - ]), - id: PropTypes.string.isRequired, - name: PropTypes.string, - label: PropTypes.string, - https: PropTypes.bool, - isRequired: PropTypes.bool, - isOptional: PropTypes.bool, - optionalLabel: PropTypes.string, - autoComplete: PropTypes.string, - placeholder: PropTypes.string, - value: PropTypes.string.isRequired, - onChange: PropTypes.func, - onInput: PropTypes.func, - error: PropTypes.string, - disabled: PropTypes.bool, - hidden: PropTypes.bool, + type: PropTypes.oneOf(["text", "password", "url", "email", "description", "number"]), + id: PropTypes.string.isRequired, + name: PropTypes.string, + label: PropTypes.string, + https: PropTypes.bool, + isRequired: PropTypes.bool, + isOptional: PropTypes.bool, + optionalLabel: PropTypes.string, + autoComplete: PropTypes.string, + placeholder: PropTypes.string, + value: PropTypes.string.isRequired, + onChange: PropTypes.func, + onInput: PropTypes.func, + error: PropTypes.string, + disabled: PropTypes.bool, + hidden: PropTypes.bool, }; export default Field; diff --git a/Client/src/Components/Inputs/Image/index.css b/Client/src/Components/Inputs/Image/index.css index 649012e1c..7492f8636 100644 --- a/Client/src/Components/Inputs/Image/index.css +++ b/Client/src/Components/Inputs/Image/index.css @@ -1,18 +1,18 @@ .MuiStack-root:has(#modal-update-picture) h1.MuiTypography-root { - font-weight: 600; + font-weight: 600; } .image-field-wrapper h2.MuiTypography-root, .MuiStack-root:has(#modal-update-picture) button, .MuiStack-root:has(#modal-update-picture) h1.MuiTypography-root { - font-size: var(--env-var-font-size-medium); + font-size: var(--env-var-font-size-medium); } .image-field-wrapper h2.MuiTypography-root { - margin-top: 10px; + margin-top: 10px; } .image-field-wrapper + p.MuiTypography-root { - margin-top: 8px; + margin-top: 8px; } .image-field-wrapper + p.MuiTypography-root, .image-field-wrapper p.MuiTypography-root { - font-size: var(--env-var-font-size-small-plus); + font-size: var(--env-var-font-size-small-plus); } diff --git a/Client/src/Components/Inputs/Image/index.jsx b/Client/src/Components/Inputs/Image/index.jsx index cdc7d8c7f..26c48eb12 100644 --- a/Client/src/Components/Inputs/Image/index.jsx +++ b/Client/src/Components/Inputs/Image/index.jsx @@ -15,133 +15,139 @@ import { checkImage } from "../../../Utils/fileUtils"; */ const ImageField = ({ id, src, loading, onChange }) => { - const theme = useTheme(); + const theme = useTheme(); - const [isDragging, setIsDragging] = useState(false); - const handleDragEnter = () => { - setIsDragging(true); - }; - const handleDragLeave = () => { - setIsDragging(false); - }; + const [isDragging, setIsDragging] = useState(false); + const handleDragEnter = () => { + setIsDragging(true); + }; + const handleDragLeave = () => { + setIsDragging(false); + }; - return ( - <> - {!checkImage(src) || loading ? ( - <> - - - - - - - - - Click to upload - {" "} - or drag and drop - - - (maximum size: 3MB) - - - - - Supported formats: JPG, PNG - - - ) : ( - - - - )} - - ); + return ( + <> + {!checkImage(src) || loading ? ( + <> + + + + + + + + + Click to upload + {" "} + or drag and drop + + + (maximum size: 3MB) + + + + + Supported formats: JPG, PNG + + + ) : ( + + + + )} + + ); }; ImageField.propTypes = { - id: PropTypes.string.isRequired, - src: PropTypes.string, - onChange: PropTypes.func.isRequired, + id: PropTypes.string.isRequired, + src: PropTypes.string, + onChange: PropTypes.func.isRequired, }; export default ImageField; diff --git a/Client/src/Components/Inputs/Radio/index.css b/Client/src/Components/Inputs/Radio/index.css index 8b1378917..e69de29bb 100644 --- a/Client/src/Components/Inputs/Radio/index.css +++ b/Client/src/Components/Inputs/Radio/index.css @@ -1 +0,0 @@ - diff --git a/Client/src/Components/Inputs/Radio/index.jsx b/Client/src/Components/Inputs/Radio/index.jsx index 1772912ac..a0874b25b 100644 --- a/Client/src/Components/Inputs/Radio/index.jsx +++ b/Client/src/Components/Inputs/Radio/index.jsx @@ -25,62 +25,62 @@ import "./index.css"; */ const Radio = (props) => { - const theme = useTheme(); + const theme = useTheme(); - return ( - } - sx={{ - color: "transparent", - width: 16, - height: 16, - boxShadow: "inset 0 0 0 1px #656a74", - mt: theme.spacing(0.5), - }} - /> - } - onChange={props.onChange} - label={ - <> - {props.title} - - {props.desc} - - - } - labelPlacement="end" - sx={{ - alignItems: "flex-start", - p: theme.spacing(2.5), - m: theme.spacing(-2.5), - borderRadius: theme.shape.borderRadius, - "&:hover": { - backgroundColor: theme.palette.background.accent, - }, - "& .MuiButtonBase-root": { - p: 0, - mr: theme.spacing(6), - }, - }} - /> - ); + return ( + } + sx={{ + color: "transparent", + width: 16, + height: 16, + boxShadow: "inset 0 0 0 1px #656a74", + mt: theme.spacing(0.5), + }} + /> + } + onChange={props.onChange} + label={ + <> + {props.title} + + {props.desc} + + + } + labelPlacement="end" + sx={{ + alignItems: "flex-start", + p: theme.spacing(2.5), + m: theme.spacing(-2.5), + borderRadius: theme.shape.borderRadius, + "&:hover": { + backgroundColor: theme.palette.background.accent, + }, + "& .MuiButtonBase-root": { + p: 0, + mr: theme.spacing(6), + }, + }} + /> + ); }; Radio.propTypes = { - title: PropTypes.string.isRequired, - desc: PropTypes.string, - size: PropTypes.string, + title: PropTypes.string.isRequired, + desc: PropTypes.string, + size: PropTypes.string, }; export default Radio; diff --git a/Client/src/Components/Inputs/Search/index.jsx b/Client/src/Components/Inputs/Search/index.jsx index b1e5d74ae..3de2cbe07 100644 --- a/Client/src/Components/Inputs/Search/index.jsx +++ b/Client/src/Components/Inputs/Search/index.jsx @@ -1,12 +1,5 @@ import PropTypes from "prop-types"; -import { - Box, - ListItem, - Autocomplete, - TextField, - Stack, - Typography, -} from "@mui/material"; +import { Box, ListItem, Autocomplete, TextField, Stack, Typography } from "@mui/material"; import { useTheme } from "@emotion/react"; import SearchIcon from "../../../assets/icons/search.svg?react"; @@ -24,172 +17,172 @@ import SearchIcon from "../../../assets/icons/search.svg?react"; */ const SearchAdornment = () => { - const theme = useTheme(); - return ( - - - - ); + const theme = useTheme(); + return ( + + + + ); }; +//TODO keep search state inside of component const Search = ({ - id, - options, - filteredBy, - secondaryLabel, - value, - inputValue, - handleInputChange, - handleChange, - sx, - multiple = false, - isAdorned = true, - error, - disabled, + id, + options, + filteredBy, + secondaryLabel, + value, + inputValue, + handleInputChange, + handleChange, + sx, + multiple = false, + isAdorned = true, + error, + disabled, }) => { - const theme = useTheme(); + const theme = useTheme(); - return ( - { - handleInputChange(newValue); - }} - onChange={(_, newValue) => { - handleChange && handleChange(newValue); - }} - fullWidth - freeSolo - disabled={disabled} - disableClearable - options={options} - getOptionLabel={(option) => option[filteredBy]} - renderInput={(params) => ( - - }), - }} - sx={{ - "& fieldset": { - borderColor: theme.palette.border.light, - borderRadius: theme.shape.borderRadius, - }, - "& .MuiOutlinedInput-root:hover:not(:has(input:focus)):not(:has(textarea:focus)) fieldset": - { - borderColor: theme.palette.border.light, - }, - }} - /> - {error && ( - - {error} - - )} - - )} - filterOptions={(options, { inputValue }) => { - const filtered = options.filter((option) => - option[filteredBy].toLowerCase().includes(inputValue.toLowerCase()) - ); + return ( + { + handleInputChange(newValue); + }} + onChange={(_, newValue) => { + handleChange && handleChange(newValue); + }} + fullWidth + freeSolo + disabled={disabled} + disableClearable + options={options} + getOptionLabel={(option) => option[filteredBy]} + renderInput={(params) => ( + + }), + }} + sx={{ + "& fieldset": { + borderColor: theme.palette.border.light, + borderRadius: theme.shape.borderRadius, + }, + "& .MuiOutlinedInput-root:hover:not(:has(input:focus)):not(:has(textarea:focus)) fieldset": + { + borderColor: theme.palette.border.light, + }, + }} + /> + {error && ( + + {error} + + )} + + )} + filterOptions={(options, { inputValue }) => { + const filtered = options.filter((option) => + option[filteredBy].toLowerCase().includes(inputValue.toLowerCase()) + ); - if (filtered.length === 0) { - return [{ [filteredBy]: "No monitors found", noOptions: true }]; - } - return filtered; - }} - renderOption={(props, option) => { - const { key, ...optionProps } = props; - return ( - - {option[filteredBy] + - (secondaryLabel ? ` (${option[secondaryLabel]})` : "")} - - ); - }} - slotProps={{ - popper: { - keepMounted: true, - sx: { - "& ul": { p: 2 }, - "& li.MuiAutocomplete-option": { - color: theme.palette.text.secondary, - px: 4, - borderRadius: theme.shape.borderRadius, - }, - "& .MuiAutocomplete-listbox .MuiAutocomplete-option[aria-selected='true'], & .MuiAutocomplete-listbox .MuiAutocomplete-option[aria-selected='true'].Mui-focused, & .MuiAutocomplete-listbox .MuiAutocomplete-option[aria-selected='true']:hover": - { - backgroundColor: theme.palette.background.fill, - }, - "& .MuiAutocomplete-noOptions": { - px: theme.spacing(6), - py: theme.spacing(5), - }, - }, - }, - }} - sx={{ - height: 34, - "&.MuiAutocomplete-root .MuiAutocomplete-input": { p: 0 }, - ...sx, - }} - /> - ); + if (filtered.length === 0) { + return [{ [filteredBy]: "No monitors found", noOptions: true }]; + } + return filtered; + }} + renderOption={(props, option) => { + const { key, ...optionProps } = props; + return ( + + {option[filteredBy] + (secondaryLabel ? ` (${option[secondaryLabel]})` : "")} + + ); + }} + slotProps={{ + popper: { + keepMounted: true, + sx: { + "& ul": { p: 2 }, + "& li.MuiAutocomplete-option": { + color: theme.palette.text.secondary, + px: 4, + borderRadius: theme.shape.borderRadius, + }, + "& .MuiAutocomplete-listbox .MuiAutocomplete-option[aria-selected='true'], & .MuiAutocomplete-listbox .MuiAutocomplete-option[aria-selected='true'].Mui-focused, & .MuiAutocomplete-listbox .MuiAutocomplete-option[aria-selected='true']:hover": + { + backgroundColor: theme.palette.background.fill, + }, + "& .MuiAutocomplete-noOptions": { + px: theme.spacing(6), + py: theme.spacing(5), + }, + }, + }, + }} + sx={{ + height: 34, + "&.MuiAutocomplete-root .MuiAutocomplete-input": { p: 0 }, + ...sx, + }} + /> + ); }; Search.propTypes = { - id: PropTypes.string, - multiple: PropTypes.bool, - options: PropTypes.array.isRequired, - filteredBy: PropTypes.string.isRequired, - secondaryLabel: PropTypes.string, - value: PropTypes.array, - inputValue: PropTypes.string.isRequired, - handleInputChange: PropTypes.func.isRequired, - handleChange: PropTypes.func, - isAdorned: PropTypes.bool, - sx: PropTypes.object, - error: PropTypes.string, - disabled: PropTypes.bool, + id: PropTypes.string, + multiple: PropTypes.bool, + options: PropTypes.array.isRequired, + filteredBy: PropTypes.string.isRequired, + secondaryLabel: PropTypes.string, + value: PropTypes.array, + inputValue: PropTypes.string.isRequired, + handleInputChange: PropTypes.func.isRequired, + handleChange: PropTypes.func, + isAdorned: PropTypes.bool, + sx: PropTypes.object, + error: PropTypes.string, + disabled: PropTypes.bool, }; export default Search; diff --git a/Client/src/Components/Inputs/Select/index.css b/Client/src/Components/Inputs/Select/index.css index 329cdd7d2..676dcc378 100644 --- a/Client/src/Components/Inputs/Select/index.css +++ b/Client/src/Components/Inputs/Select/index.css @@ -1,7 +1,7 @@ .select-wrapper .select-component > .MuiSelect-select { - padding: 0 10px; - height: 34px; - display: flex; - align-items: center; - line-height: 1; + padding: 0 10px; + height: 34px; + display: flex; + align-items: center; + line-height: 1; } diff --git a/Client/src/Components/Inputs/Select/index.jsx b/Client/src/Components/Inputs/Select/index.jsx index 00cceaa29..e470881ec 100644 --- a/Client/src/Components/Inputs/Select/index.jsx +++ b/Client/src/Components/Inputs/Select/index.jsx @@ -1,11 +1,6 @@ import PropTypes from "prop-types"; import { useTheme } from "@emotion/react"; -import { - MenuItem, - Select as MuiSelect, - Stack, - Typography, -} from "@mui/material"; +import { MenuItem, Select as MuiSelect, Stack, Typography } from "@mui/material"; import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown"; import "./index.css"; @@ -44,105 +39,108 @@ import "./index.css"; */ const Select = ({ - id, - label, - placeholder, - isHidden, - value, - items, - onChange, - sx, - name = "", + id, + label, + placeholder, + isHidden, + value, + items, + onChange, + sx, + name = "", }) => { - const theme = useTheme(); - const itemStyles = { - fontSize: "var(--env-var-font-size-medium)", - color: theme.palette.text.tertiary, - borderRadius: theme.shape.borderRadius, - margin: theme.spacing(2), - }; + const theme = useTheme(); + const itemStyles = { + fontSize: "var(--env-var-font-size-medium)", + color: theme.palette.text.tertiary, + borderRadius: theme.shape.borderRadius, + margin: theme.spacing(2), + }; - return ( - - {label && ( - - {label} - - )} - - {placeholder && ( - - {placeholder} - - )} - {items.map((item) => ( - - {item.name} - - ))} - - - ); + return ( + + {label && ( + + {label} + + )} + + {placeholder && ( + + {placeholder} + + )} + {items.map((item) => ( + + {item.name} + + ))} + + + ); }; Select.propTypes = { - id: PropTypes.string.isRequired, - name: PropTypes.string, - label: PropTypes.string, - placeholder: PropTypes.string, - isHidden: PropTypes.bool, - value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, - items: PropTypes.arrayOf( - PropTypes.shape({ - _id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, - name: PropTypes.string.isRequired, - }) - ).isRequired, - onChange: PropTypes.func.isRequired, - sx: PropTypes.object, + id: PropTypes.string.isRequired, + name: PropTypes.string, + label: PropTypes.string, + placeholder: PropTypes.string, + isHidden: PropTypes.bool, + value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, + items: PropTypes.arrayOf( + PropTypes.shape({ + _id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, + name: PropTypes.string.isRequired, + }) + ).isRequired, + onChange: PropTypes.func.isRequired, + sx: PropTypes.object, }; export default Select; diff --git a/Client/src/Components/Label/index.css b/Client/src/Components/Label/index.css index 961e6c128..104d43410 100644 --- a/Client/src/Components/Label/index.css +++ b/Client/src/Components/Label/index.css @@ -1,7 +1,7 @@ .label { - border: 1px solid #000; - display: inline-flex; - justify-content: center; - align-items: center; - line-height: normal; + border: 1px solid #000; + display: inline-flex; + justify-content: center; + align-items: center; + line-height: normal; } diff --git a/Client/src/Components/Label/index.jsx b/Client/src/Components/Label/index.jsx index bdc34982a..966bd0098 100644 --- a/Client/src/Components/Label/index.jsx +++ b/Client/src/Components/Label/index.jsx @@ -20,56 +20,56 @@ import "./index.css"; */ const BaseLabel = ({ label, styles, children }) => { - const theme = useTheme(); - // Grab the default borderRadius from the theme to match button style - const { borderRadius } = theme.shape; - // Calculate padding for the label to mimic button. Appears to scale correctly, not 100% sure though. - const padding = theme.spacing(1 * 0.75, 2); + const theme = useTheme(); + // Grab the default borderRadius from the theme to match button style + const { borderRadius } = theme.shape; + // Calculate padding for the label to mimic button. Appears to scale correctly, not 100% sure though. + const padding = theme.spacing(1 * 0.75, 2); - return ( - - {children} - {label} - - ); + return ( + + {children} + {label} + + ); }; BaseLabel.propTypes = { - label: PropTypes.string.isRequired, - styles: PropTypes.shape({ - color: PropTypes.string, - backgroundColor: PropTypes.string, - }), - children: PropTypes.node, + label: PropTypes.string.isRequired, + styles: PropTypes.shape({ + color: PropTypes.string, + backgroundColor: PropTypes.string, + }), + children: PropTypes.node, }; // Produces a lighter color based on a hex color and a percent // lightenColor("#067647", 20) will produce a color 20% lighter than #067647 const lightenColor = (color, percent) => { - let r = parseInt(color.substring(1, 3), 16); - let g = parseInt(color.substring(3, 5), 16); - let b = parseInt(color.substring(5, 7), 16); + let r = parseInt(color.substring(1, 3), 16); + let g = parseInt(color.substring(3, 5), 16); + let b = parseInt(color.substring(5, 7), 16); - const amt = Math.round((255 * percent) / 100); + const amt = Math.round((255 * percent) / 100); - r = r + amt <= 255 ? r + amt : 255; - g = g + amt <= 255 ? g + amt : 255; - b = b + amt <= 255 ? b + amt : 255; + r = r + amt <= 255 ? r + amt : 255; + g = g + amt <= 255 ? g + amt : 255; + b = b + amt <= 255 ? b + amt : 255; - r = r.toString(16).padStart(2, "0"); - g = g.toString(16).padStart(2, "0"); - b = b.toString(16).padStart(2, "0"); + r = r.toString(16).padStart(2, "0"); + g = g.toString(16).padStart(2, "0"); + b = b.toString(16).padStart(2, "0"); - return `#${r}${g}${b}`; + return `#${r}${g}${b}`; }; /** @@ -84,34 +84,31 @@ const lightenColor = (color, percent) => { */ const ColoredLabel = ({ label, color }) => { - const theme = useTheme(); - // If an invalid color is passed, default to the labelGray color - if ( - typeof color !== "string" || - !/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(color) - ) { - color = theme.palette.border.light; - } + const theme = useTheme(); + // If an invalid color is passed, default to the labelGray color + if (typeof color !== "string" || !/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(color)) { + color = theme.palette.border.light; + } - // Calculate lighter shades for border and bg - const borderColor = lightenColor(color, 20); - const bgColor = lightenColor(color, 75); + // Calculate lighter shades for border and bg + const borderColor = lightenColor(color, 20); + const bgColor = lightenColor(color, 75); - return ( - - ); + return ( + + ); }; ColoredLabel.propTypes = { - label: PropTypes.string.isRequired, - color: PropTypes.string.isRequired, + label: PropTypes.string.isRequired, + color: PropTypes.string.isRequired, }; /** @@ -126,69 +123,63 @@ ColoredLabel.propTypes = { */ const StatusLabel = ({ status, text, customStyles }) => { - const theme = useTheme(); - const colors = { - up: { - dotColor: theme.palette.success.main, - bgColor: theme.palette.success.bg, - borderColor: theme.palette.success.light, - }, - down: { - dotColor: theme.palette.error.text, - bgColor: theme.palette.error.bg, - borderColor: theme.palette.error.light, - }, - paused: { - dotColor: theme.palette.warning.main, - bgColor: theme.palette.warning.bg, - borderColor: theme.palette.warning.light, - }, - pending: { - dotColor: theme.palette.warning.main, - bgColor: theme.palette.warning.bg, - borderColor: theme.palette.warning.light, - }, - "cannot resolve": { - dotColor: theme.palette.unresolved.main, - bgColor: theme.palette.unresolved.bg, - borderColor: theme.palette.unresolved.light, - }, - }; + const theme = useTheme(); + const colors = { + up: { + dotColor: theme.palette.success.main, + bgColor: theme.palette.success.bg, + borderColor: theme.palette.success.light, + }, + down: { + dotColor: theme.palette.error.text, + bgColor: theme.palette.error.bg, + borderColor: theme.palette.error.light, + }, + paused: { + dotColor: theme.palette.warning.main, + bgColor: theme.palette.warning.bg, + borderColor: theme.palette.warning.light, + }, + pending: { + dotColor: theme.palette.warning.main, + bgColor: theme.palette.warning.bg, + borderColor: theme.palette.warning.light, + }, + "cannot resolve": { + dotColor: theme.palette.unresolved.main, + bgColor: theme.palette.unresolved.bg, + borderColor: theme.palette.unresolved.light, + }, + }; - // Look up the color for the status - const { borderColor, bgColor, dotColor } = colors[status]; + // Look up the color for the status + const { borderColor, bgColor, dotColor } = colors[status]; - return ( - - - - ); + return ( + + + + ); }; StatusLabel.propTypes = { - status: PropTypes.oneOf([ - "up", - "down", - "paused", - "pending", - "cannot resolve", - ]), - text: PropTypes.string, - customStyles: PropTypes.object, + status: PropTypes.oneOf(["up", "down", "paused", "pending", "cannot resolve"]), + text: PropTypes.string, + customStyles: PropTypes.object, }; export { ColoredLabel, StatusLabel }; diff --git a/Client/src/Components/Link/index.jsx b/Client/src/Components/Link/index.jsx index 5a055e28e..e280cf0cc 100644 --- a/Client/src/Components/Link/index.jsx +++ b/Client/src/Components/Link/index.jsx @@ -11,52 +11,52 @@ import PropTypes from "prop-types"; */ const Link = ({ level, label, url }) => { - const theme = useTheme(); + const theme = useTheme(); - const levelConfig = { - primary: {}, - secondary: { - color: theme.palette.text.secondary, - sx: { - ":hover": { - color: theme.palette.text.secondary, - }, - }, - }, - tertiary: { - color: theme.palette.text.tertiary, - sx: { - textDecoration: "underline", - textDecorationStyle: "dashed", - textDecorationColor: theme.palette.primary.main, - textUnderlineOffset: "1px", - ":hover": { - color: theme.palette.text.tertiary, - textDecorationColor: theme.palette.primary.main, - backgroundColor: theme.palette.background.fill, - }, - }, - }, - error: {}, - }; - const { sx, color } = levelConfig[level]; - return ( - - {label} - - ); + const levelConfig = { + primary: {}, + secondary: { + color: theme.palette.text.secondary, + sx: { + ":hover": { + color: theme.palette.text.secondary, + }, + }, + }, + tertiary: { + color: theme.palette.text.tertiary, + sx: { + textDecoration: "underline", + textDecorationStyle: "dashed", + textDecorationColor: theme.palette.primary.main, + textUnderlineOffset: "1px", + ":hover": { + color: theme.palette.text.tertiary, + textDecorationColor: theme.palette.primary.main, + backgroundColor: theme.palette.background.fill, + }, + }, + }, + error: {}, + }; + const { sx, color } = levelConfig[level]; + return ( + + {label} + + ); }; Link.propTypes = { - url: PropTypes.string.isRequired, - level: PropTypes.oneOf(["primary", "secondary", "tertiary", "error"]), - label: PropTypes.string.isRequired, + url: PropTypes.string.isRequired, + level: PropTypes.oneOf(["primary", "secondary", "tertiary", "error"]), + label: PropTypes.string.isRequired, }; export default Link; diff --git a/Client/src/Components/ProgressBars/index.css b/Client/src/Components/ProgressBars/index.css index 09e4915da..9b4ab2c7d 100644 --- a/Client/src/Components/ProgressBars/index.css +++ b/Client/src/Components/ProgressBars/index.css @@ -1,10 +1,10 @@ .progress-bar-container h2.MuiTypography-root, .progress-bar-container p.MuiTypography-root { - font-size: var(--env-var-font-size-medium); + font-size: var(--env-var-font-size-medium); } .progress-bar-container p.MuiTypography-root:has(span) { - font-size: 12px; + font-size: 12px; } .progress-bar-container p.MuiTypography-root span { - padding-left: 2px; + padding-left: 2px; } diff --git a/Client/src/Components/ProgressBars/index.jsx b/Client/src/Components/ProgressBars/index.jsx index d1dcc6cfe..46b2efab1 100644 --- a/Client/src/Components/ProgressBars/index.jsx +++ b/Client/src/Components/ProgressBars/index.jsx @@ -1,13 +1,7 @@ import React from "react"; import { useTheme } from "@emotion/react"; import PropTypes from "prop-types"; -import { - Box, - IconButton, - LinearProgress, - Stack, - Typography, -} from "@mui/material"; +import { Box, IconButton, LinearProgress, Stack, Typography } from "@mui/material"; import CloseIcon from "@mui/icons-material/Close"; import ErrorOutlineOutlinedIcon from "@mui/icons-material/ErrorOutlineOutlined"; import "./index.css"; @@ -23,161 +17,163 @@ import "./index.css"; * @returns {JSX.Element} The rendered component. */ -const ProgressUpload = ({ - icon, - label, - size, - progress = 0, - onClick, - error, -}) => { - const theme = useTheme(); +const ProgressUpload = ({ icon, label, size, progress = 0, onClick, error }) => { + const theme = useTheme(); - return ( - .MuiStack-root > svg": { - fill: theme.palette.error.text, - width: "20px", - height: "20px", - }, - }, - }} - > - - {error ? ( - - ) : icon ? ( - - {icon} - - ) : ( - "" - )} - {error ? ( - - {error} - - ) : ( - - - {error ? error : label} - - - {!error && size} - - - )} - - - - - {!error ? ( - - - - - - {progress} - % - - - ) : ( - "" - )} - - ); + return ( + .MuiStack-root > svg": { + fill: theme.palette.error.text, + width: "20px", + height: "20px", + }, + }, + }} + > + + {error ? ( + + ) : icon ? ( + + {icon} + + ) : ( + "" + )} + {error ? ( + + {error} + + ) : ( + + + {error ? error : label} + + + {!error && size} + + + )} + + + + + {!error ? ( + + + + + + {progress} + % + + + ) : ( + "" + )} + + ); }; ProgressUpload.propTypes = { - icon: PropTypes.element, // JSX element for the icon (optional) - label: PropTypes.string.isRequired, // Label text for the progress item - size: PropTypes.string.isRequired, // Size information for the progress item - progress: PropTypes.number.isRequired, // Current progress value (0-100) - onClick: PropTypes.func.isRequired, // Function to handle click events on the remove button - error: PropTypes.string, // Error message to display if there's an error (optional) + icon: PropTypes.element, // JSX element for the icon (optional) + label: PropTypes.string.isRequired, // Label text for the progress item + size: PropTypes.string.isRequired, // Size information for the progress item + progress: PropTypes.number.isRequired, // Current progress value (0-100) + onClick: PropTypes.func.isRequired, // Function to handle click events on the remove button + error: PropTypes.string, // Error message to display if there's an error (optional) }; export default ProgressUpload; diff --git a/Client/src/Components/ProgressStepper/index.jsx b/Client/src/Components/ProgressStepper/index.jsx index 532fd1023..80dd193d6 100644 --- a/Client/src/Components/ProgressStepper/index.jsx +++ b/Client/src/Components/ProgressStepper/index.jsx @@ -5,17 +5,17 @@ import CheckCircle from "@mui/icons-material/CheckCircle"; import PropTypes from "prop-types"; const CustomStepIcon = (props) => { - const { completed, active } = props; - return completed ? ( - - ) : ( - - ); + const { completed, active } = props; + return completed ? ( + + ) : ( + + ); }; CustomStepIcon.propTypes = { - completed: PropTypes.bool.isRequired, - active: PropTypes.bool.isRequired, + completed: PropTypes.bool.isRequired, + active: PropTypes.bool.isRequired, }; /** @@ -25,34 +25,43 @@ CustomStepIcon.propTypes = { */ const ProgressStepper = ({ steps }) => { - const [activeStep, setActiveStep] = React.useState(1); - return ( - - {steps.map((step, index) => { - const color = activeStep === index ? "primary" : "inherit"; - return ( - setActiveStep(index)}> - - - {step.label} - - - - {step.content} - - - ); - })} - - ); + const [activeStep, setActiveStep] = React.useState(1); + return ( + + {steps.map((step, index) => { + const color = activeStep === index ? "primary" : "inherit"; + return ( + setActiveStep(index)} + > + + + {step.label} + + + + {step.content} + + + ); + })} + + ); }; ProgressStepper.propTypes = { - steps: PropTypes.array.isRequired, + steps: PropTypes.array.isRequired, }; export default ProgressStepper; diff --git a/Client/src/Components/ProtectedRoute/index.jsx b/Client/src/Components/ProtectedRoute/index.jsx index 5665e9317..5b1032ab8 100644 --- a/Client/src/Components/ProtectedRoute/index.jsx +++ b/Client/src/Components/ProtectedRoute/index.jsx @@ -14,17 +14,20 @@ import PropTypes from "prop-types"; */ const ProtectedRoute = ({ Component, ...rest }) => { - const authState = useSelector((state) => state.auth); + const authState = useSelector((state) => state.auth); - return authState.authToken ? ( - - ) : ( - - ); + return authState.authToken ? ( + + ) : ( + + ); }; ProtectedRoute.propTypes = { - Component: PropTypes.elementType.isRequired, + Component: PropTypes.elementType.isRequired, }; export default ProtectedRoute; diff --git a/Client/src/Components/Sidebar/index.css b/Client/src/Components/Sidebar/index.css index b01562ac4..049591e0c 100644 --- a/Client/src/Components/Sidebar/index.css +++ b/Client/src/Components/Sidebar/index.css @@ -1,94 +1,94 @@ aside .MuiList-root svg { - width: 20px; - height: 20px; - opacity: 0.9; + width: 20px; + height: 20px; + opacity: 0.9; } aside span.MuiTypography-root { - font-size: var(--env-var-font-size-medium); - line-height: 1; + font-size: var(--env-var-font-size-medium); + line-height: 1; } aside .MuiStack-root + span.MuiTypography-root { - font-size: var(--env-var-font-size-medium-plus); + font-size: var(--env-var-font-size-medium-plus); } aside .MuiListSubheader-root { - font-size: var(--env-var-font-size-small); - font-weight: 500; - line-height: 1.5; - text-transform: uppercase; - margin-bottom: 2px; - opacity: 0.6; + font-size: var(--env-var-font-size-small); + font-weight: 500; + line-height: 1.5; + text-transform: uppercase; + margin-bottom: 2px; + opacity: 0.6; } aside p.MuiTypography-root { - font-size: var(--env-var-font-size-small); - opacity: 0.8; + font-size: var(--env-var-font-size-small); + opacity: 0.8; } aside .MuiListItemButton-root:not(.selected-path) > * { - opacity: 0.9; + opacity: 0.9; } aside .selected-path > * { - opacity: 1; + opacity: 1; } aside .selected-path span.MuiTypography-root { - font-weight: 600; + font-weight: 600; } aside .MuiCollapse-wrapperInner .MuiList-root > .MuiListItemButton-root { - position: relative; + position: relative; } aside .MuiCollapse-wrapperInner .MuiList-root svg, aside .MuiList-root .MuiListItemText-root + svg { - width: 18px; - height: 18px; + width: 18px; + height: 18px; } .sidebar-popup li.MuiButtonBase-root:has(.MuiBox-root) { - padding-bottom: 0; + padding-bottom: 0; } .sidebar-popup svg { - width: 16px; - height: 16px; - opacity: 0.9; + width: 16px; + height: 16px; + opacity: 0.9; } /* TRANSITIONS */ aside { - flex: 1; - transition: max-width 650ms cubic-bezier(0.36, -0.01, 0, 0.77); + flex: 1; + transition: max-width 650ms cubic-bezier(0.36, -0.01, 0, 0.77); } .home-layout aside.collapsed { - max-width: 64px; + max-width: 64px; } aside.expanded .MuiTypography-root, aside.expanded p.MuiTypography-root, aside.expanded .MuiListItemText-root + svg, aside.expanded .MuiAvatar-root + .MuiBox-root + .MuiIconButton-root { - visibility: visible; - animation: fadeIn 1s ease; + visibility: visible; + animation: fadeIn 1s ease; } aside.collapsed .MuiTypography-root, aside.collapsed p.MuiTypography-root, aside.collapsed .MuiListItemText-root + svg, aside.collapsed .MuiAvatar-root + .MuiBox-root + .MuiIconButton-root { - opacity: 0; - visibility: hidden; + opacity: 0; + visibility: hidden; } aside .MuiListSubheader-root { - transition: padding 200ms ease; + transition: padding 200ms ease; } @keyframes fadeIn { - 0% { - opacity: 0; - visibility: hidden; - } - 30% { - opacity: 0; - visibility: hidden; - } - 100% { - opacity: 0.9; - visibility: visible; - } + 0% { + opacity: 0; + visibility: hidden; + } + 30% { + opacity: 0; + visibility: hidden; + } + 100% { + opacity: 0.9; + visibility: visible; + } } diff --git a/Client/src/Components/Sidebar/index.jsx b/Client/src/Components/Sidebar/index.jsx index 0019a282a..d4e7c9fd2 100644 --- a/Client/src/Components/Sidebar/index.jsx +++ b/Client/src/Components/Sidebar/index.jsx @@ -1,19 +1,19 @@ import React, { useEffect, useState } from "react"; import { - Box, - Collapse, - Divider, - IconButton, - List, - ListItemButton, - ListItemIcon, - ListItemText, - ListSubheader, - Menu, - MenuItem, - Stack, - Tooltip, - Typography, + Box, + Collapse, + Divider, + IconButton, + List, + ListItemButton, + ListItemIcon, + ListItemText, + ListSubheader, + Menu, + MenuItem, + Stack, + Tooltip, + Typography, } from "@mui/material"; import { useLocation, useNavigate } from "react-router"; import { useTheme } from "@emotion/react"; @@ -48,50 +48,50 @@ import Folder from "../../assets/icons/folder.svg?react"; import "./index.css"; const menu = [ - { - name: "Dashboard", - icon: , - nested: [ - { name: "Monitors", path: "monitors", icon: }, - { name: "Pagespeed", path: "pagespeed", icon: }, - ], - }, - { name: "Incidents", path: "incidents", icon: }, - // { name: "Status pages", path: "status", icon: }, - { name: "Maintenance", path: "maintenance", icon: }, - // { name: "Integrations", path: "integrations", icon: }, - { - name: "Account", - icon: , - nested: [ - { name: "Profile", path: "account/profile", icon: }, - { name: "Password", path: "account/password", icon: }, - { name: "Team", path: "account/team", icon: }, - ], - }, - { - name: "Other", - icon: , - nested: [ - { name: "Settings", path: "settings", icon: }, - { name: "Support", path: "support", icon: }, - { name: "Docs", path: "docs", icon: }, - { name: "Changelog", path: "changelog", icon: }, - ], - }, + { + name: "Dashboard", + icon: , + nested: [ + { name: "Monitors", path: "monitors", icon: }, + { name: "Pagespeed", path: "pagespeed", icon: }, + ], + }, + { name: "Incidents", path: "incidents", icon: }, + // { name: "Status pages", path: "status", icon: }, + { name: "Maintenance", path: "maintenance", icon: }, + // { name: "Integrations", path: "integrations", icon: }, + { + name: "Account", + icon: , + nested: [ + { name: "Profile", path: "account/profile", icon: }, + { name: "Password", path: "account/password", icon: }, + { name: "Team", path: "account/team", icon: }, + ], + }, + { + name: "Other", + icon: , + nested: [ + { name: "Settings", path: "settings", icon: }, + { name: "Support", path: "support", icon: }, + { name: "Docs", path: "docs", icon: }, + { name: "Changelog", path: "changelog", icon: }, + ], + }, ]; const URL_MAP = { - support: "https://github.com/bluewave-labs/bluewave-uptime/issues", - docs: "https://bluewavelabs.gitbook.io/uptime-manager", - changelog: "https://github.com/bluewave-labs/bluewave-uptime/releases", + support: "https://github.com/bluewave-labs/bluewave-uptime/issues", + docs: "https://bluewavelabs.gitbook.io/uptime-manager", + changelog: "https://github.com/bluewave-labs/bluewave-uptime/releases", }; const PATH_MAP = { - monitors: "Dashboard", - pagespeed: "Dashboard", - account: "Account", - settings: "Other", + monitors: "Dashboard", + pagespeed: "Dashboard", + account: "Account", + settings: "Other", }; /** @@ -102,508 +102,519 @@ const PATH_MAP = { */ function Sidebar() { - const theme = useTheme(); - const navigate = useNavigate(); - const location = useLocation(); - const dispatch = useDispatch(); - const authState = useSelector((state) => state.auth); - const collapsed = useSelector((state) => state.ui.sidebar.collapsed); - const [open, setOpen] = useState({ Dashboard: false, Account: false, Other: false }); - const [anchorEl, setAnchorEl] = useState(null); - const [popup, setPopup] = useState(); - const { user } = useSelector((state) => state.auth); + const theme = useTheme(); + const navigate = useNavigate(); + const location = useLocation(); + const dispatch = useDispatch(); + const authState = useSelector((state) => state.auth); + const collapsed = useSelector((state) => state.ui.sidebar.collapsed); + const [open, setOpen] = useState({ Dashboard: false, Account: false, Other: false }); + const [anchorEl, setAnchorEl] = useState(null); + const [popup, setPopup] = useState(); + const { user } = useSelector((state) => state.auth); - // Remove demo password if demo - const accountMenuItem = menu.find((item) => item.name === "Account"); - if (user.role?.includes("demo") && accountMenuItem) { - accountMenuItem.nested = accountMenuItem.nested.filter((item) => { - return item.name !== "Password"; - }); - } + // Remove demo password if demo + const accountMenuItem = menu.find((item) => item.name === "Account"); + if (user.role?.includes("demo") && accountMenuItem) { + accountMenuItem.nested = accountMenuItem.nested.filter((item) => { + return item.name !== "Password"; + }); + } - const openPopup = (event, id) => { - setAnchorEl(event.currentTarget); - setPopup(id); - }; - const closePopup = () => { - setAnchorEl(null); - }; + const openPopup = (event, id) => { + setAnchorEl(event.currentTarget); + setPopup(id); + }; + const closePopup = () => { + setAnchorEl(null); + }; - /** - * Handles logging out the user - * - */ - const logout = async () => { - // Clear auth state - dispatch(clearAuthState()); - dispatch(clearUptimeMonitorState()); - navigate("/login"); - }; + /** + * Handles logging out the user + * + */ + const logout = async () => { + // Clear auth state + dispatch(clearAuthState()); + dispatch(clearUptimeMonitorState()); + navigate("/login"); + }; - useEffect(() => { - const matchedKey = Object.keys(PATH_MAP).find((key) => - location.pathname.includes(key) - ); + useEffect(() => { + const matchedKey = Object.keys(PATH_MAP).find((key) => + location.pathname.includes(key) + ); - if (matchedKey) { - setOpen((prev) => ({ ...prev, [PATH_MAP[matchedKey]]: true })); - } - }, []); + if (matchedKey) { + setOpen((prev) => ({ ...prev, [PATH_MAP[matchedKey]]: true })); + } + }, []); - return ( - - - - - BU - - - BlueWave Uptime - - - { - setOpen(prev => - Object.fromEntries(Object.keys(prev).map(key => [key, false])) - ) - dispatch(toggleSidebar()); - }} - > - {collapsed ? : } - - - {/* menu */} - - Menu - - } - sx={{ px: theme.spacing(6) }} - > - {menu.map((item) => - item.path ? ( - - navigate(`/${item.path}`)} - sx={{ - height: "37px", - gap: theme.spacing(4), - borderRadius: theme.shape.borderRadius, - px: theme.spacing(4), - }} - > - {item.icon} - {item.name} - - - ) : collapsed ? ( - - - openPopup(event, item.name)} - sx={{ - position: "relative", - gap: theme.spacing(4), - borderRadius: theme.shape.borderRadius, - px: theme.spacing(4), - }} - > - {item.icon} - {item.name} - - - - {item.nested.map((child) => { - if ( - child.name === "Team" && - authState.user?.role && - !authState.user.role.includes("superadmin") - ) { - return null; - } + return ( + + + + + BU + + + BlueWave Uptime + + + { + setOpen((prev) => + Object.fromEntries(Object.keys(prev).map((key) => [key, false])) + ); + dispatch(toggleSidebar()); + }} + > + {collapsed ? : } + + + {/* menu */} + + Menu + + } + sx={{ px: theme.spacing(6) }} + > + {menu.map((item) => + item.path ? ( + + navigate(`/${item.path}`)} + sx={{ + height: "37px", + gap: theme.spacing(4), + borderRadius: theme.shape.borderRadius, + px: theme.spacing(4), + }} + > + {item.icon} + {item.name} + + + ) : collapsed ? ( + + + openPopup(event, item.name)} + sx={{ + position: "relative", + gap: theme.spacing(4), + borderRadius: theme.shape.borderRadius, + px: theme.spacing(4), + }} + > + {item.icon} + {item.name} + + + + {item.nested.map((child) => { + if ( + child.name === "Team" && + authState.user?.role && + !authState.user.role.includes("superadmin") + ) { + return null; + } - return ( - { - const url = URL_MAP[child.path]; - if (url) { - window.open(url, "_blank", "noreferrer"); - } else { - navigate(`/${child.path}`); - } - closePopup(); - }} - sx={{ - gap: theme.spacing(4), - opacity: 0.9, - "& svg": { - "& path": { - stroke: theme.palette.other.icon, - strokeWidth: 1.1, - }, - }, - }} - > - {child.icon} - {child.name} - - ); - })} - - - ) : ( - - - setOpen((prev) => ({ - ...Object.fromEntries(Object.keys(prev).map(key => [key, false])), - [item.name]: !prev[item.name] - })) - } - sx={{ - gap: theme.spacing(4), - borderRadius: theme.shape.borderRadius, - px: theme.spacing(4), - }} - > - {item.icon} - {item.name} - {open[`${item.name}`] ? : } - - - - {item.nested.map((child) => { - if ( - child.name === "Team" && - authState.user?.role && - !authState.user.role.includes("superadmin") - ) { - return null; - } + return ( + { + const url = URL_MAP[child.path]; + if (url) { + window.open(url, "_blank", "noreferrer"); + } else { + navigate(`/${child.path}`); + } + closePopup(); + }} + sx={{ + gap: theme.spacing(4), + opacity: 0.9, + "& svg": { + "& path": { + stroke: theme.palette.other.icon, + strokeWidth: 1.1, + }, + }, + }} + > + {child.icon} + {child.name} + + ); + })} + + + ) : ( + + + setOpen((prev) => ({ + ...Object.fromEntries(Object.keys(prev).map((key) => [key, false])), + [item.name]: !prev[item.name], + })) + } + sx={{ + gap: theme.spacing(4), + borderRadius: theme.shape.borderRadius, + px: theme.spacing(4), + }} + > + {item.icon} + {item.name} + {open[`${item.name}`] ? : } + + + + {item.nested.map((child) => { + if ( + child.name === "Team" && + authState.user?.role && + !authState.user.role.includes("superadmin") + ) { + return null; + } - return ( - { - const url = URL_MAP[child.path]; - if (url) { - window.open(url, "_blank", "noreferrer"); - } else { - navigate(`/${child.path}`); - } - }} - sx={{ - gap: theme.spacing(4), - borderRadius: theme.shape.borderRadius, - pl: theme.spacing(4), - "&::before": { - content: `""`, - position: "absolute", - top: 0, - left: "-7px", - height: "100%", - borderLeft: 1, - borderLeftColor: theme.palette.other.line, - }, - "&:last-child::before": { - height: "50%", - }, - "&::after": { - content: `""`, - position: "absolute", - top: "45%", - left: "-8px", - height: "3px", - width: "3px", - borderRadius: "50%", - backgroundColor: theme.palette.other.line, - }, - "&.selected-path::after": { - backgroundColor: theme.palette.text.tertiary, - transform: "scale(1.2)", - }, - }} - > - - {child.icon} - - {child.name} - - ); - })} - - - - ) - )} - - + return ( + { + const url = URL_MAP[child.path]; + if (url) { + window.open(url, "_blank", "noreferrer"); + } else { + navigate(`/${child.path}`); + } + }} + sx={{ + gap: theme.spacing(4), + borderRadius: theme.shape.borderRadius, + pl: theme.spacing(4), + "&::before": { + content: `""`, + position: "absolute", + top: 0, + left: "-7px", + height: "100%", + borderLeft: 1, + borderLeftColor: theme.palette.other.line, + }, + "&:last-child::before": { + height: "50%", + }, + "&::after": { + content: `""`, + position: "absolute", + top: "45%", + left: "-8px", + height: "3px", + width: "3px", + borderRadius: "50%", + backgroundColor: theme.palette.other.line, + }, + "&.selected-path::after": { + backgroundColor: theme.palette.text.tertiary, + transform: "scale(1.2)", + }, + }} + > + {child.icon} + {child.name} + + ); + })} + + + + ) + )} + + - - {collapsed ? ( - <> - - openPopup(event, "logout")} - sx={{ p: 0, "&:focus": { outline: "none" } }} - > - - - - - ) : ( - <> - - - - {authState.user?.firstName} {authState.user?.lastName} - - - {authState.user?.role} - - - - openPopup(event, "logout")} - > - - - - - )} - - {collapsed && ( - - - - {authState.user?.firstName} {authState.user?.lastName} - - - {authState.user?.role} - - - - )} - {collapsed && } - {/* + {collapsed ? ( + <> + + openPopup(event, "logout")} + sx={{ p: 0, "&:focus": { outline: "none" } }} + > + + + + + ) : ( + <> + + + + {authState.user?.firstName} {authState.user?.lastName} + + + {authState.user?.role} + + + + openPopup(event, "logout")} + > + + + + + )} + + {collapsed && ( + + + + {authState.user?.firstName} {authState.user?.lastName} + + + {authState.user?.role} + + + + )} + {collapsed && } + {/* { dispatch(setMode("light")); closePopup(); @@ -619,25 +630,25 @@ function Sidebar() { > Dark */} - - - - Log out - - - - - ); + + + + Log out + + + + + ); } export default Sidebar; diff --git a/Client/src/Components/TabPanels/Account/PasswordPanel.jsx b/Client/src/Components/TabPanels/Account/PasswordPanel.jsx index 182cd16df..700e27fb9 100644 --- a/Client/src/Components/TabPanels/Account/PasswordPanel.jsx +++ b/Client/src/Components/TabPanels/Account/PasswordPanel.jsx @@ -17,183 +17,186 @@ import { createToast } from "../../../Utils/toastUtils"; */ const PasswordPanel = () => { - const theme = useTheme(); - const dispatch = useDispatch(); + const theme = useTheme(); + const dispatch = useDispatch(); - //redux state - const { authToken, isLoading } = useSelector((state) => state.auth); + //redux state + const { authToken, isLoading } = useSelector((state) => state.auth); - const idToName = { - "edit-current-password": "password", - "edit-new-password": "newPassword", - "edit-confirm-password": "confirm", - }; + const idToName = { + "edit-current-password": "password", + "edit-new-password": "newPassword", + "edit-confirm-password": "confirm", + }; - const [localData, setLocalData] = useState({ - password: "", - newPassword: "", - confirm: "", - }); - const [errors, setErrors] = useState({}); + const [localData, setLocalData] = useState({ + password: "", + newPassword: "", + confirm: "", + }); + const [errors, setErrors] = useState({}); - const handleChange = (event) => { - const { value, id } = event.target; - const name = idToName[id]; - setLocalData((prev) => ({ - ...prev, - [name]: value, - })); + const handleChange = (event) => { + const { value, id } = event.target; + const name = idToName[id]; + setLocalData((prev) => ({ + ...prev, + [name]: value, + })); - const validation = credentials.validate( - { [name]: value }, - { abortEarly: false, context: { password: localData.newPassword } } - ); + const validation = credentials.validate( + { [name]: value }, + { abortEarly: false, context: { password: localData.newPassword } } + ); - setErrors((prev) => { - const updatedErrors = { ...prev }; + setErrors((prev) => { + const updatedErrors = { ...prev }; - if (validation.error) { - updatedErrors[name] = validation.error.details[0].message; - } else { - delete updatedErrors[name]; - } - return updatedErrors; - }); - }; + if (validation.error) { + updatedErrors[name] = validation.error.details[0].message; + } else { + delete updatedErrors[name]; + } + return updatedErrors; + }); + }; - const handleSubmit = async (event) => { - event.preventDefault(); + const handleSubmit = async (event) => { + event.preventDefault(); - const { error } = credentials.validate(localData, { - abortEarly: false, - context: { password: localData.newPassword }, - }); + const { error } = credentials.validate(localData, { + abortEarly: false, + context: { password: localData.newPassword }, + }); - if (error) { - const newErrors = {}; - error.details.forEach((err) => { - newErrors[err.path[0]] = err.message; - }); - setErrors(newErrors); - } else { - const action = await dispatch(update({ authToken, localData })); - if (action.payload.success) { - createToast({ - body: "Your password was changed successfully.", - }); - setLocalData({ - password: "", - newPassword: "", - confirm: "", - }); - } else { - // TODO: Check for other errors? - createToast({ - body: "Your password input was incorrect.", - }); - setErrors({ password: "*" + action.payload.msg + "." }); - } - } - }; + if (error) { + const newErrors = {}; + error.details.forEach((err) => { + newErrors[err.path[0]] = err.message; + }); + setErrors(newErrors); + } else { + const action = await dispatch(update({ authToken, localData })); + if (action.payload.success) { + createToast({ + body: "Your password was changed successfully.", + }); + setLocalData({ + password: "", + newPassword: "", + confirm: "", + }); + } else { + // TODO: Check for other errors? + createToast({ + body: "Your password input was incorrect.", + }); + setErrors({ password: "*" + action.payload.msg + "." }); + } + } + }; - return ( - - - - - Current password - - - - - New password - - - - - - Confirm new password - - - - - - - - - - - - Save - - - - - ); + return ( + + + + + Current password + + + + + New password + + + + + + Confirm new password + + + + + + + + + + + + Save + + + + + ); }; PasswordPanel.propTypes = { - // No props are being passed to this component, hence no specific PropTypes are defined. + // No props are being passed to this component, hence no specific PropTypes are defined. }; export default PasswordPanel; diff --git a/Client/src/Components/TabPanels/Account/ProfilePanel.jsx b/Client/src/Components/TabPanels/Account/ProfilePanel.jsx index efc2db6ae..999e026c7 100644 --- a/Client/src/Components/TabPanels/Account/ProfilePanel.jsx +++ b/Client/src/Components/TabPanels/Account/ProfilePanel.jsx @@ -7,11 +7,7 @@ import Field from "../../Inputs/Field"; import ImageField from "../../Inputs/Image"; import { credentials, imageValidation } from "../../../Validation/validation"; import { useDispatch, useSelector } from "react-redux"; -import { - clearAuthState, - deleteUser, - update, -} from "../../../Features/Auth/authSlice"; +import { clearAuthState, deleteUser, update } from "../../../Features/Auth/authSlice"; import ImageIcon from "@mui/icons-material/Image"; import ProgressUpload from "../../ProgressBars"; import { formatBytes } from "../../../Utils/fileUtils"; @@ -29,471 +25,508 @@ import LoadingButton from "@mui/lab/LoadingButton"; */ const ProfilePanel = () => { - const theme = useTheme(); - const dispatch = useDispatch(); + const theme = useTheme(); + const dispatch = useDispatch(); - //redux state - const { user, authToken, isLoading } = useSelector((state) => state.auth); + //redux state + const { user, authToken, isLoading } = useSelector((state) => state.auth); - const idToName = { - "edit-first-name": "firstName", - "edit-last-name": "lastName", - // Disabled for now, will revisit in the future - // "edit-email": "email", - }; + const idToName = { + "edit-first-name": "firstName", + "edit-last-name": "lastName", + // Disabled for now, will revisit in the future + // "edit-email": "email", + }; - // Local state for form data, errors, and file handling - const [localData, setLocalData] = useState({ - firstName: user.firstName, - lastName: user.lastName, - // email: user.email, // Disabled for now - }); - const [errors, setErrors] = useState({}); - const [file, setFile] = useState(); - const intervalRef = useRef(null); - const [progress, setProgress] = useState({ value: 0, isLoading: false }); + // Local state for form data, errors, and file handling + const [localData, setLocalData] = useState({ + firstName: user.firstName, + lastName: user.lastName, + // email: user.email, // Disabled for now + }); + const [errors, setErrors] = useState({}); + const [file, setFile] = useState(); + const intervalRef = useRef(null); + const [progress, setProgress] = useState({ value: 0, isLoading: false }); - // Handles input field changes and performs validation - const handleChange = (event) => { - errors["unchanged"] && clearError("unchanged"); - const { value, id } = event.target; - const name = idToName[id]; - setLocalData((prev) => ({ - ...prev, - [name]: value, - })); + // Handles input field changes and performs validation + const handleChange = (event) => { + errors["unchanged"] && clearError("unchanged"); + const { value, id } = event.target; + const name = idToName[id]; + setLocalData((prev) => ({ + ...prev, + [name]: value, + })); - validateField({ [name]: value }, credentials, name); - }; + validateField({ [name]: value }, credentials, name); + }; - // Handles image file - const handlePicture = (event) => { - const pic = event.target.files[0]; - let error = validateField( - { type: pic.type, size: pic.size }, - imageValidation - ); - if (error) return; + // Handles image file + const handlePicture = (event) => { + const pic = event.target.files[0]; + let error = validateField({ type: pic.type, size: pic.size }, imageValidation); + if (error) return; - setProgress((prev) => ({ ...prev, isLoading: true })); - setFile({ - src: URL.createObjectURL(pic), - name: pic.name, - size: formatBytes(pic.size), - delete: false, - }); + setProgress((prev) => ({ ...prev, isLoading: true })); + setFile({ + src: URL.createObjectURL(pic), + name: pic.name, + size: formatBytes(pic.size), + delete: false, + }); - //TODO - potentitally remove, will revisit in the future - intervalRef.current = setInterval(() => { - const buffer = 12; - setProgress((prev) => { - if (prev.value + buffer >= 100) { - clearInterval(intervalRef.current); - return { value: 100, isLoading: false }; - } - return { ...prev, value: prev.value + buffer }; - }); - }, 120); - }; + //TODO - potentitally remove, will revisit in the future + intervalRef.current = setInterval(() => { + const buffer = 12; + setProgress((prev) => { + if (prev.value + buffer >= 100) { + clearInterval(intervalRef.current); + return { value: 100, isLoading: false }; + } + return { ...prev, value: prev.value + buffer }; + }); + }, 120); + }; - // Validates input against provided schema and updates error state - const validateField = (toValidate, schema, name = "picture") => { - const { error } = schema.validate(toValidate, { abortEarly: false }); - setErrors((prev) => { - const prevErrors = { ...prev }; - if (error) prevErrors[name] = error.details[0].message; - else delete prevErrors[name]; - return prevErrors; - }); - if (error) return true; - }; + // Validates input against provided schema and updates error state + const validateField = (toValidate, schema, name = "picture") => { + const { error } = schema.validate(toValidate, { abortEarly: false }); + setErrors((prev) => { + const prevErrors = { ...prev }; + if (error) prevErrors[name] = error.details[0].message; + else delete prevErrors[name]; + return prevErrors; + }); + if (error) return true; + }; - // Clears specific error from errors state - const clearError = (err) => { - setErrors((prev) => { - const updatedErrors = { ...prev }; - if (updatedErrors[err]) delete updatedErrors[err]; - return updatedErrors; - }); - }; + // Clears specific error from errors state + const clearError = (err) => { + setErrors((prev) => { + const updatedErrors = { ...prev }; + if (updatedErrors[err]) delete updatedErrors[err]; + return updatedErrors; + }); + }; - // Resets picture-related states and clears interval - const removePicture = () => { - errors["picture"] && clearError("picture"); - setFile({ delete: true }); - clearInterval(intervalRef.current); // interrupt interval if image upload is canceled prior to completing the process - setProgress({ value: 0, isLoading: false }); - }; + // Resets picture-related states and clears interval + const removePicture = () => { + errors["picture"] && clearError("picture"); + setFile({ delete: true }); + clearInterval(intervalRef.current); // interrupt interval if image upload is canceled prior to completing the process + setProgress({ value: 0, isLoading: false }); + }; - // Opens the picture update modal - const openPictureModal = () => { - setIsOpen("picture"); - setFile({ delete: localData.deleteProfileImage }); - }; + // Opens the picture update modal + const openPictureModal = () => { + setIsOpen("picture"); + setFile({ delete: localData.deleteProfileImage }); + }; - // Closes the picture update modal and resets related states - const closePictureModal = () => { - errors["picture"] && clearError("picture"); - setFile(); //reset file - clearInterval(intervalRef.current); // interrupt interval if image upload is canceled prior to completing the process - setProgress({ value: 0, isLoading: false }); - setIsOpen(""); - }; + // Closes the picture update modal and resets related states + const closePictureModal = () => { + errors["picture"] && clearError("picture"); + setFile(); //reset file + clearInterval(intervalRef.current); // interrupt interval if image upload is canceled prior to completing the process + setProgress({ value: 0, isLoading: false }); + setIsOpen(""); + }; - // Updates profile image displayed on UI - const handleUpdatePicture = () => { - setProgress({ value: 0, isLoading: false }); - setLocalData((prev) => ({ - ...prev, - file: file.src, - deleteProfileImage: false, - })); - setIsOpen(""); - errors["unchanged"] && clearError("unchanged"); - }; + // Updates profile image displayed on UI + const handleUpdatePicture = () => { + setProgress({ value: 0, isLoading: false }); + setLocalData((prev) => ({ + ...prev, + file: file.src, + deleteProfileImage: false, + })); + setIsOpen(""); + errors["unchanged"] && clearError("unchanged"); + }; - // Handles form submission to update user profile - const handleSaveProfile = async (event) => { - event.preventDefault(); - if ( - localData.firstName === user.firstName && - localData.lastName === user.lastName && - localData.deleteProfileImage === undefined && - localData.file === undefined - ) { - createToast({ - body: "Unable to update profile — no changes detected.", - }); - setErrors({ unchanged: "unable to update profile" }); - return; - } + // Handles form submission to update user profile + const handleSaveProfile = async (event) => { + event.preventDefault(); + if ( + localData.firstName === user.firstName && + localData.lastName === user.lastName && + localData.deleteProfileImage === undefined && + localData.file === undefined + ) { + createToast({ + body: "Unable to update profile — no changes detected.", + }); + setErrors({ unchanged: "unable to update profile" }); + return; + } - const action = await dispatch(update({ authToken, localData })); - if (action.payload.success) { - createToast({ - body: "Your profile data was changed successfully.", - }); - } else { - createToast({ - body: "There was an error updating your profile data.", - }); - } - }; + const action = await dispatch(update({ authToken, localData })); + if (action.payload.success) { + createToast({ + body: "Your profile data was changed successfully.", + }); + } else { + createToast({ + body: "There was an error updating your profile data.", + }); + } + }; - // Removes current profile image from UI - const handleDeletePicture = () => { - setLocalData((prev) => ({ - ...prev, - deleteProfileImage: true, - })); - errors["unchanged"] && clearError("unchanged"); - }; + // Removes current profile image from UI + const handleDeletePicture = () => { + setLocalData((prev) => ({ + ...prev, + deleteProfileImage: true, + })); + errors["unchanged"] && clearError("unchanged"); + }; - // Initiates the account deletion process - const handleDeleteAccount = async () => { - const action = await dispatch(deleteUser(authToken)); - if (action.payload.success) { - dispatch(clearAuthState()); - dispatch(clearUptimeMonitorState()); - } else { - if (action.payload) { - // dispatch errors - createToast({ - body: action.payload.msg, - }); - } else { - // unknown errors - createToast({ - body: "Unknown error.", - }); - } - } - }; + // Initiates the account deletion process + const handleDeleteAccount = async () => { + const action = await dispatch(deleteUser(authToken)); + if (action.payload.success) { + dispatch(clearAuthState()); + dispatch(clearUptimeMonitorState()); + } else { + if (action.payload) { + // dispatch errors + createToast({ + body: action.payload.msg, + }); + } else { + // unknown errors + createToast({ + body: "Unknown error.", + }); + } + } + }; - // Modal state and control functions - const [isOpen, setIsOpen] = useState(""); - const isModalOpen = (name) => isOpen === name; + // Modal state and control functions + const [isOpen, setIsOpen] = useState(""); + const isModalOpen = (name) => isOpen === name; - return ( - - - - - First name - - - - - - Last name - - - - - - Email - - This is your current email address — it cannot be changed. - - - logger.warn("disabled")} - // error={errors[idToName["edit-email"]]} - disabled={true} - /> - - - - Your photo - - This photo will be displayed in your profile page. - - - - - - - - - - - ); + return ( + + + + + First name + + + + + + Last name + + + + + + Email + + This is your current email address — it cannot be changed. + + + logger.warn("disabled")} + // error={errors[idToName["edit-email"]]} + disabled={true} + /> + + + + Your photo + + This photo will be displayed in your profile page. + + + + + + + + + + + ); }; ProfilePanel.propTypes = { - // No props are being passed to this component, hence no specific PropTypes are defined. + // No props are being passed to this component, hence no specific PropTypes are defined. }; export default ProfilePanel; diff --git a/Client/src/Components/TabPanels/Account/TeamPanel.jsx b/Client/src/Components/TabPanels/Account/TeamPanel.jsx index f5a6e86b5..33218bcbb 100644 --- a/Client/src/Components/TabPanels/Account/TeamPanel.jsx +++ b/Client/src/Components/TabPanels/Account/TeamPanel.jsx @@ -1,13 +1,6 @@ import { useTheme } from "@emotion/react"; import TabPanel from "@mui/lab/TabPanel"; -import { - Box, - Button, - ButtonGroup, - Modal, - Stack, - Typography, -} from "@mui/material"; +import { Box, Button, ButtonGroup, Modal, Stack, Typography } from "@mui/material"; import { useEffect, useState } from "react"; import Field from "../../Inputs/Field"; import { credentials } from "../../../Validation/validation"; @@ -27,216 +20,211 @@ import LoadingButton from "@mui/lab/LoadingButton"; */ const TeamPanel = () => { - const roleMap = { - superadmin: "Super admin", - admin: "Admin", - user: "Team member", - demo: "Demo User", - }; + const roleMap = { + superadmin: "Super admin", + admin: "Admin", + user: "Team member", + demo: "Demo User", + }; - const theme = useTheme(); + const theme = useTheme(); - const { authToken, user } = useSelector((state) => state.auth); - //TODO - const [orgStates, setOrgStates] = useState({ - name: "Bluewave Labs", - isEdit: false, - }); - const [toInvite, setToInvite] = useState({ - email: "", - role: ["0"], - }); - const [tableData, setTableData] = useState({}); - const [members, setMembers] = useState([]); - const [filter, setFilter] = useState("all"); - const [isDisabled, setIsDisabled] = useState(true); - const [errors, setErrors] = useState({}); - const [isSendingInvite, setIsSendingInvite] = useState(false); + const { authToken, user } = useSelector((state) => state.auth); + //TODO + const [orgStates, setOrgStates] = useState({ + name: "Bluewave Labs", + isEdit: false, + }); + const [toInvite, setToInvite] = useState({ + email: "", + role: ["0"], + }); + const [tableData, setTableData] = useState({}); + const [members, setMembers] = useState([]); + const [filter, setFilter] = useState("all"); + const [isDisabled, setIsDisabled] = useState(true); + const [errors, setErrors] = useState({}); + const [isSendingInvite, setIsSendingInvite] = useState(false); - useEffect(() => { - const fetchTeam = async () => { - try { - const response = await networkService.getAllUsers({ - authToken: authToken, - }); - setMembers(response.data.data); - } catch (error) { - createToast({ - body: error.message || "Error fetching team members.", - }); - } - }; + useEffect(() => { + const fetchTeam = async () => { + try { + const response = await networkService.getAllUsers({ + authToken: authToken, + }); + setMembers(response.data.data); + } catch (error) { + createToast({ + body: error.message || "Error fetching team members.", + }); + } + }; - fetchTeam(); - }, [user]); + fetchTeam(); + }, [user]); - useEffect(() => { - let team = members; - if (filter !== "all") - team = members.filter((member) => { - if (filter === "admin") { - return ( - member.role.includes("admin") || member.role.includes("superadmin") - ); - } - return member.role.includes(filter); - }); + useEffect(() => { + let team = members; + if (filter !== "all") + team = members.filter((member) => { + if (filter === "admin") { + return member.role.includes("admin") || member.role.includes("superadmin"); + } + return member.role.includes(filter); + }); - const data = { - cols: [ - { id: 1, name: "NAME" }, - { id: 2, name: "EMAIL" }, - { id: 3, name: "ROLE" }, - // FEATURE STILL TO BE IMPLEMENTED - // { id: 4, name: "ACTION" }, - ], - rows: team?.map((member, idx) => { - const roles = member.role.map((role) => roleMap[role]).join(","); - return { - id: member._id, - data: [ - { - id: idx, - data: ( - - - {member.firstName + " " + member.lastName} - - - Created {new Date(member.createdAt).toLocaleDateString()} - - - ), - }, - { id: idx + 1, data: member.email }, - { - // TODO - Add select dropdown - id: idx + 2, - data: roles, - }, - // FEATURE STILL TO BE IMPLEMENTED - // { - // // TODO - Add delete onClick - // id: idx + 3, - // data: ( - // - // - // - // ), - // }, - ], - }; - }), - }; + const data = { + cols: [ + { id: 1, name: "NAME" }, + { id: 2, name: "EMAIL" }, + { id: 3, name: "ROLE" }, + // FEATURE STILL TO BE IMPLEMENTED + // { id: 4, name: "ACTION" }, + ], + rows: team?.map((member, idx) => { + const roles = member.role.map((role) => roleMap[role]).join(","); + return { + id: member._id, + data: [ + { + id: idx, + data: ( + + + {member.firstName + " " + member.lastName} + + + Created {new Date(member.createdAt).toLocaleDateString()} + + + ), + }, + { id: idx + 1, data: member.email }, + { + // TODO - Add select dropdown + id: idx + 2, + data: roles, + }, + // FEATURE STILL TO BE IMPLEMENTED + // { + // // TODO - Add delete onClick + // id: idx + 3, + // data: ( + // + // + // + // ), + // }, + ], + }; + }), + }; - setTableData(data); - }, [members, filter]); - useEffect(() => { - setIsDisabled(Object.keys(errors).length !== 0 || toInvite.email === ""); - }, [errors, toInvite.email]); + setTableData(data); + }, [members, filter]); + useEffect(() => { + setIsDisabled(Object.keys(errors).length !== 0 || toInvite.email === ""); + }, [errors, toInvite.email]); - // RENAME ORGANIZATION - const toggleEdit = () => { - setOrgStates((prev) => ({ ...prev, isEdit: !prev.isEdit })); - }; - const handleRename = () => {}; + // RENAME ORGANIZATION + const toggleEdit = () => { + setOrgStates((prev) => ({ ...prev, isEdit: !prev.isEdit })); + }; + const handleRename = () => {}; - // INVITE MEMBER - const [isOpen, setIsOpen] = useState(false); + // INVITE MEMBER + const [isOpen, setIsOpen] = useState(false); - const handleChange = (event) => { - const { value } = event.target; - setToInvite((prev) => ({ - ...prev, - email: value, - })); + const handleChange = (event) => { + const { value } = event.target; + setToInvite((prev) => ({ + ...prev, + email: value, + })); - const validation = credentials.validate( - { email: value }, - { abortEarly: false } - ); + const validation = credentials.validate({ email: value }, { abortEarly: false }); - setErrors((prev) => { - const updatedErrors = { ...prev }; + setErrors((prev) => { + const updatedErrors = { ...prev }; - if (validation.error) { - updatedErrors.email = validation.error.details[0].message; - } else { - delete updatedErrors.email; - } - return updatedErrors; - }); - }; + if (validation.error) { + updatedErrors.email = validation.error.details[0].message; + } else { + delete updatedErrors.email; + } + return updatedErrors; + }); + }; - const handleInviteMember = async () => { - if (!toInvite.email) { - setErrors((prev) => ({ ...prev, email: "Email is required." })); - return; - } - setIsSendingInvite(true); - if (!toInvite.role.includes("user") || !toInvite.role.includes("admin")) - setToInvite((prev) => ({ ...prev, role: ["user"] })); + const handleInviteMember = async () => { + if (!toInvite.email) { + setErrors((prev) => ({ ...prev, email: "Email is required." })); + return; + } + setIsSendingInvite(true); + if (!toInvite.role.includes("user") || !toInvite.role.includes("admin")) + setToInvite((prev) => ({ ...prev, role: ["user"] })); - const { error } = credentials.validate( - { email: toInvite.email }, - { - abortEarly: false, - } - ); + const { error } = credentials.validate( + { email: toInvite.email }, + { + abortEarly: false, + } + ); - if (error) { - setErrors((prev) => ({ ...prev, email: error.details[0].message })); - return; - } + if (error) { + setErrors((prev) => ({ ...prev, email: error.details[0].message })); + return; + } - try { - await networkService.requestInvitationToken({ - authToken: authToken, - email: toInvite.email, - role: toInvite.role, - }); - closeInviteModal(); - createToast({ - body: "Member invited. They will receive an email with details on how to create their account.", - }); - } catch (error) { - createToast({ - body: error.message || "Unknown error.", - }); - } finally { - setIsSendingInvite(false); - } - }; + try { + await networkService.requestInvitationToken({ + authToken: authToken, + email: toInvite.email, + role: toInvite.role, + }); + closeInviteModal(); + createToast({ + body: "Member invited. They will receive an email with details on how to create their account.", + }); + } catch (error) { + createToast({ + body: error.message || "Unknown error.", + }); + } finally { + setIsSendingInvite(false); + } + }; - const closeInviteModal = () => { - setIsOpen(false); - setToInvite({ email: "", role: ["0"] }); - setErrors({}); - }; + const closeInviteModal = () => { + setIsOpen(false); + setToInvite({ email: "", role: ["0"] }); + setErrors({}); + }; - return ( - - {/* FEATURE STILL TO BE IMPLEMENTED */} - {/* + return ( + + {/* FEATURE STILL TO BE IMPLEMENTED */} + {/* Organization name @@ -282,161 +270,163 @@ const TeamPanel = () => { + ); }; TeamPanel.propTypes = { - // No props are being passed to this component, hence no specific PropTypes are defined. + // No props are being passed to this component, hence no specific PropTypes are defined. }; export default TeamPanel; diff --git a/Client/src/Features/Auth/authSlice.js b/Client/src/Features/Auth/authSlice.js index 7c8588b52..31c8cb89f 100644 --- a/Client/src/Features/Auth/authSlice.js +++ b/Client/src/Features/Auth/authSlice.js @@ -4,270 +4,254 @@ import { jwtDecode } from "jwt-decode"; import axios from "axios"; const initialState = { - isLoading: false, - authToken: "", - user: "", - success: null, - msg: null, + isLoading: false, + authToken: "", + user: "", + success: null, + msg: null, }; -export const register = createAsyncThunk( - "auth/register", - async (form, thunkApi) => { - try { - const res = await networkService.registerUser(form); - return res.data; - } catch (error) { - if (error.response.data) { - return thunkApi.rejectWithValue(error.response.data); - } - const payload = { - status: false, - msg: error.message ? error.message : "Unknown error", - }; - return thunkApi.rejectWithValue(payload); - } - } -); - -export const login = createAsyncThunk("auth/login", async (form, thunkApi) => { - try { - const res = await networkService.loginUser(form); - return res.data; - } catch (error) { - if (error.response && error.response.data) { - return thunkApi.rejectWithValue(error.response.data); - } - const payload = { - status: false, - msg: error.message ? error.message : "Unknown error", - }; - return thunkApi.rejectWithValue(payload); - } +export const register = createAsyncThunk("auth/register", async (form, thunkApi) => { + try { + const res = await networkService.registerUser(form); + return res.data; + } catch (error) { + if (error.response.data) { + return thunkApi.rejectWithValue(error.response.data); + } + const payload = { + status: false, + msg: error.message ? error.message : "Unknown error", + }; + return thunkApi.rejectWithValue(payload); + } }); -export const update = createAsyncThunk( - "auth/update", - async (data, thunkApi) => { - const { authToken: token, localData: form } = data; - const user = jwtDecode(token); - try { - const fd = new FormData(); - form.firstName && fd.append("firstName", form.firstName); - form.lastName && fd.append("lastName", form.lastName); - form.password && fd.append("password", form.password); - form.newPassword && fd.append("newPassword", form.newPassword); - if (form.file && form.file !== "") { - const imageResult = await axios.get(form.file, { - responseType: "blob", - }); - fd.append("profileImage", imageResult.data); - } - form.deleteProfileImage && - fd.append("deleteProfileImage", form.deleteProfileImage); +export const login = createAsyncThunk("auth/login", async (form, thunkApi) => { + try { + const res = await networkService.loginUser(form); + return res.data; + } catch (error) { + if (error.response && error.response.data) { + return thunkApi.rejectWithValue(error.response.data); + } + const payload = { + status: false, + msg: error.message ? error.message : "Unknown error", + }; + return thunkApi.rejectWithValue(payload); + } +}); - const res = await networkService.updateUser({ - authToken: token, - userId: user._id, - form: fd, - }); - return res.data; - } catch (error) { - if (error.response && error.response.data) { - return thunkApi.rejectWithValue(error.response.data); - } - const payload = { - status: false, - msg: error.message ? error.message : "Unknown error", - }; - return thunkApi.rejectWithValue(payload); - } - } -); +export const update = createAsyncThunk("auth/update", async (data, thunkApi) => { + const { authToken: token, localData: form } = data; + const user = jwtDecode(token); + try { + const fd = new FormData(); + form.firstName && fd.append("firstName", form.firstName); + form.lastName && fd.append("lastName", form.lastName); + form.password && fd.append("password", form.password); + form.newPassword && fd.append("newPassword", form.newPassword); + if (form.file && form.file !== "") { + const imageResult = await axios.get(form.file, { + responseType: "blob", + }); + fd.append("profileImage", imageResult.data); + } + form.deleteProfileImage && fd.append("deleteProfileImage", form.deleteProfileImage); -export const deleteUser = createAsyncThunk( - "auth/delete", - async (data, thunkApi) => { - const user = jwtDecode(data); + const res = await networkService.updateUser({ + authToken: token, + userId: user._id, + form: fd, + }); + return res.data; + } catch (error) { + if (error.response && error.response.data) { + return thunkApi.rejectWithValue(error.response.data); + } + const payload = { + status: false, + msg: error.message ? error.message : "Unknown error", + }; + return thunkApi.rejectWithValue(payload); + } +}); - try { - const res = await networkService.deleteUser({ - authToken: data, - userId: user._id, - }); - return res.data; - } catch (error) { - if (error.response && error.response.data) { - return thunkApi.rejectWithValue(error.response.data); - } - const payload = { - status: false, - msg: error.message ? error.message : "Unknown error", - }; - return thunkApi.rejectWithValue(payload); - } - } -); +export const deleteUser = createAsyncThunk("auth/delete", async (data, thunkApi) => { + const user = jwtDecode(data); + + try { + const res = await networkService.deleteUser({ + authToken: data, + userId: user._id, + }); + return res.data; + } catch (error) { + if (error.response && error.response.data) { + return thunkApi.rejectWithValue(error.response.data); + } + const payload = { + status: false, + msg: error.message ? error.message : "Unknown error", + }; + return thunkApi.rejectWithValue(payload); + } +}); export const forgotPassword = createAsyncThunk( - "auth/forgotPassword", - async (form, thunkApi) => { - try { - const res = await networkService.forgotPassword(form); - return res.data; - } catch (error) { - if (error.response.data) { - return thunkApi.rejectWithValue(error.response.data); - } - const payload = { - status: false, - msg: error.message ? error.message : "Unknown error", - }; - return thunkApi.rejectWithValue(payload); - } - } + "auth/forgotPassword", + async (form, thunkApi) => { + try { + const res = await networkService.forgotPassword(form); + return res.data; + } catch (error) { + if (error.response.data) { + return thunkApi.rejectWithValue(error.response.data); + } + const payload = { + status: false, + msg: error.message ? error.message : "Unknown error", + }; + return thunkApi.rejectWithValue(payload); + } + } ); export const setNewPassword = createAsyncThunk( - "auth/setNewPassword", - async (data, thunkApi) => { - const { token, form } = data; - try { - await networkService.validateRecoveryToken({ recoveryToken: token }); - const res = await networkService.setNewPassword({ - recoveryToken: token, - form: form, - }); - return res.data; - } catch (error) { - if (error.response.data) { - return thunkApi.rejectWithValue(error.response.data); - } - const payload = { - status: false, - msg: error.message ? error.message : "Unknown error", - }; - return thunkApi.rejectWithValue(payload); - } - } + "auth/setNewPassword", + async (data, thunkApi) => { + const { token, form } = data; + try { + await networkService.validateRecoveryToken({ recoveryToken: token }); + const res = await networkService.setNewPassword({ + recoveryToken: token, + form: form, + }); + return res.data; + } catch (error) { + if (error.response.data) { + return thunkApi.rejectWithValue(error.response.data); + } + const payload = { + status: false, + msg: error.message ? error.message : "Unknown error", + }; + return thunkApi.rejectWithValue(payload); + } + } ); const handleAuthFulfilled = (state, action) => { - state.isLoading = false; - state.success = action.payload.success; - state.msg = action.payload.msg; - state.authToken = action.payload.data.token; - state.user = action.payload.data.user; + state.isLoading = false; + state.success = action.payload.success; + state.msg = action.payload.msg; + state.authToken = action.payload.data.token; + state.user = action.payload.data.user; }; const handleAuthRejected = (state, action) => { - state.isLoading = false; - state.success = false; - state.msg = action.payload - ? action.payload.msg - : "Failed to login or register"; + state.isLoading = false; + state.success = false; + state.msg = action.payload ? action.payload.msg : "Failed to login or register"; }; const handleUpdateFulfilled = (state, action) => { - state.isLoading = false; - state.success = action.payload.success; - state.msg = action.payload.msg; - state.user = action.payload.data; + state.isLoading = false; + state.success = action.payload.success; + state.msg = action.payload.msg; + state.user = action.payload.data; }; const handleUpdateRejected = (state, action) => { - state.isLoading = false; - state.success = false; - state.msg = action.payload - ? action.payload.msg - : "Failed to update profile data."; + state.isLoading = false; + state.success = false; + state.msg = action.payload ? action.payload.msg : "Failed to update profile data."; }; const handleDeleteFulfilled = (state, action) => { - state.isLoading = false; - state.success = action.payload.success; - state.msg = action.payload.msg; + state.isLoading = false; + state.success = action.payload.success; + state.msg = action.payload.msg; }; const handleDeleteRejected = (state, action) => { - state.isLoading = false; - state.success = false; - state.msg = action.payload ? action.payload.msg : "Failed to delete account."; + state.isLoading = false; + state.success = false; + state.msg = action.payload ? action.payload.msg : "Failed to delete account."; }; const handleForgotFulfilled = (state, action) => { - state.isLoading = false; - state.success = action.payload.success; - state.msg = action.payload.msg; + state.isLoading = false; + state.success = action.payload.success; + state.msg = action.payload.msg; }; const handleForgotRejected = (state, action) => { - state.isLoading = false; - state.success = false; - state.msg = action.payload - ? action.payload.msg - : "Failed to send reset instructions."; + state.isLoading = false; + state.success = false; + state.msg = action.payload ? action.payload.msg : "Failed to send reset instructions."; }; const handleNewPasswordRejected = (state, action) => { - state.isLoading = false; - state.success = false; - state.msg = action.payload ? action.payload.msg : "Failed to reset password."; + state.isLoading = false; + state.success = false; + state.msg = action.payload ? action.payload.msg : "Failed to reset password."; }; const authSlice = createSlice({ - name: "auth", - initialState, - reducers: { - clearAuthState: (state) => { - state.authToken = ""; - state.user = ""; - state.isLoading = false; - state.success = true; - state.msg = "Logged out successfully"; - }, - }, - extraReducers: (builder) => { - // Register thunk - builder - .addCase(register.pending, (state) => { - state.isLoading = true; - }) - .addCase(register.fulfilled, handleAuthFulfilled) - .addCase(register.rejected, handleAuthRejected); + name: "auth", + initialState, + reducers: { + clearAuthState: (state) => { + state.authToken = ""; + state.user = ""; + state.isLoading = false; + state.success = true; + state.msg = "Logged out successfully"; + }, + }, + extraReducers: (builder) => { + // Register thunk + builder + .addCase(register.pending, (state) => { + state.isLoading = true; + }) + .addCase(register.fulfilled, handleAuthFulfilled) + .addCase(register.rejected, handleAuthRejected); - // Login thunk - builder - .addCase(login.pending, (state) => { - state.isLoading = true; - }) - .addCase(login.fulfilled, handleAuthFulfilled) - .addCase(login.rejected, handleAuthRejected); + // Login thunk + builder + .addCase(login.pending, (state) => { + state.isLoading = true; + }) + .addCase(login.fulfilled, handleAuthFulfilled) + .addCase(login.rejected, handleAuthRejected); - // Update thunk - builder - .addCase(update.pending, (state) => { - state.isLoading = true; - }) - .addCase(update.fulfilled, handleUpdateFulfilled) - .addCase(update.rejected, handleUpdateRejected); + // Update thunk + builder + .addCase(update.pending, (state) => { + state.isLoading = true; + }) + .addCase(update.fulfilled, handleUpdateFulfilled) + .addCase(update.rejected, handleUpdateRejected); - // Delete thunk - builder - .addCase(deleteUser.pending, (state) => { - state.isLoading = true; - }) - .addCase(deleteUser.fulfilled, handleDeleteFulfilled) - .addCase(deleteUser.rejected, handleDeleteRejected); + // Delete thunk + builder + .addCase(deleteUser.pending, (state) => { + state.isLoading = true; + }) + .addCase(deleteUser.fulfilled, handleDeleteFulfilled) + .addCase(deleteUser.rejected, handleDeleteRejected); - // Forgot password thunk - builder - .addCase(forgotPassword.pending, (state) => { - state.isLoading = true; - }) - .addCase(forgotPassword.fulfilled, handleForgotFulfilled) - .addCase(forgotPassword.rejected, handleForgotRejected); + // Forgot password thunk + builder + .addCase(forgotPassword.pending, (state) => { + state.isLoading = true; + }) + .addCase(forgotPassword.fulfilled, handleForgotFulfilled) + .addCase(forgotPassword.rejected, handleForgotRejected); - // Set new password thunk - builder - .addCase(setNewPassword.pending, (state) => { - state.isLoading = true; - }) - .addCase(setNewPassword.fulfilled, handleAuthFulfilled) - .addCase(setNewPassword.rejected, handleNewPasswordRejected); - }, + // Set new password thunk + builder + .addCase(setNewPassword.pending, (state) => { + state.isLoading = true; + }) + .addCase(setNewPassword.fulfilled, handleAuthFulfilled) + .addCase(setNewPassword.rejected, handleNewPasswordRejected); + }, }); export default authSlice.reducer; diff --git a/Client/src/Features/PageSpeedMonitor/pageSpeedMonitorSlice.js b/Client/src/Features/PageSpeedMonitor/pageSpeedMonitorSlice.js index 0322a5143..6cd91ac28 100644 --- a/Client/src/Features/PageSpeedMonitor/pageSpeedMonitorSlice.js +++ b/Client/src/Features/PageSpeedMonitor/pageSpeedMonitorSlice.js @@ -2,283 +2,324 @@ import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"; import { jwtDecode } from "jwt-decode"; import { networkService } from "../../main"; const initialState = { - isLoading: false, - monitorsSummary: [], - success: null, - msg: null, + isLoading: false, + monitorsSummary: [], + success: null, + msg: null, }; export const createPageSpeed = createAsyncThunk( - "pageSpeedMonitors/createPageSpeed", - async (data, thunkApi) => { - try { - const { authToken, monitor } = data; - const res = await networkService.createMonitor({ - authToken: authToken, - monitor: monitor, - }); - return res.data; - } catch (error) { - if (error.response && error.response.data) { - return thunkApi.rejectWithValue(error.response.data); - } - const payload = { - status: false, - msg: error.message ? error.message : "Unknown error", - }; - return thunkApi.rejectWithValue(payload); - } - } + "pageSpeedMonitors/createPageSpeed", + async (data, thunkApi) => { + try { + const { authToken, monitor } = data; + const res = await networkService.createMonitor({ + authToken: authToken, + monitor: monitor, + }); + return res.data; + } catch (error) { + if (error.response && error.response.data) { + return thunkApi.rejectWithValue(error.response.data); + } + const payload = { + status: false, + msg: error.message ? error.message : "Unknown error", + }; + return thunkApi.rejectWithValue(payload); + } + } ); +export const checkEndpointResolution = createAsyncThunk( + "monitors/checkEndpoint", + async (data, thunkApi) => { + try { + const { authToken, monitorURL } = data; + + const res = await networkService.checkEndpointResolution({ + authToken: authToken, + monitorURL: monitorURL, + }) + return res.data; + } catch (error) { + if (error.response && error.response.data) { + return thunkApi.rejectWithValue(error.response.data); + } + const payload = { + status: false, + msg: error.message ? error.message : "Unknown error", + }; + return thunkApi.rejectWithValue(payload); + } + } +) + export const getPagespeedMonitorById = createAsyncThunk( - "monitors/getMonitorById", - async (data, thunkApi) => { - try { - const { authToken, monitorId } = data; - const res = await networkService.getMonitorById({ - authToken: authToken, - monitorId: monitorId, - }); - return res.data; - } catch (error) { - if (error.response && error.response.data) { - return thunkApi.rejectWithValue(error.response.data); - } - const payload = { - status: false, - msg: error.message ? error.message : "Unknown error", - }; - return thunkApi.rejectWithValue(payload); - } - } + "monitors/getMonitorById", + async (data, thunkApi) => { + try { + const { authToken, monitorId } = data; + const res = await networkService.getMonitorById({ + authToken: authToken, + monitorId: monitorId, + }); + return res.data; + } catch (error) { + if (error.response && error.response.data) { + return thunkApi.rejectWithValue(error.response.data); + } + const payload = { + status: false, + msg: error.message ? error.message : "Unknown error", + }; + return thunkApi.rejectWithValue(payload); + } + } ); export const getPageSpeedByTeamId = createAsyncThunk( - "pageSpeedMonitors/getPageSpeedByTeamId", - async (token, thunkApi) => { - const user = jwtDecode(token); - try { - const res = await networkService.getMonitorsAndSummaryByTeamId({ - authToken: token, - teamId: user.teamId, - types: ["pagespeed"], - }); + "pageSpeedMonitors/getPageSpeedByTeamId", + async (token, thunkApi) => { + const user = jwtDecode(token); + try { + const res = await networkService.getMonitorsAndSummaryByTeamId({ + authToken: token, + teamId: user.teamId, + types: ["pagespeed"], + }); - return res.data; - } catch (error) { - if (error.response && error.response.data) { - return thunkApi.rejectWithValue(error.response.data); - } - const payload = { - status: false, - msg: error.message ? error.message : "Unknown error", - }; - return thunkApi.rejectWithValue(payload); - } - } + return res.data; + } catch (error) { + if (error.response && error.response.data) { + return thunkApi.rejectWithValue(error.response.data); + } + const payload = { + status: false, + msg: error.message ? error.message : "Unknown error", + }; + return thunkApi.rejectWithValue(payload); + } + } ); export const updatePageSpeed = createAsyncThunk( - "pageSpeedMonitors/updatePageSpeed", - async (data, thunkApi) => { - try { - const { authToken, monitor } = data; - const updatedFields = { - name: monitor.name, - description: monitor.description, - interval: monitor.interval, - // notifications: monitor.notifications, - }; - const res = await networkService.updateMonitor({ - authToken: authToken, - monitorId: monitor._id, - updatedFields: updatedFields, - }); - return res.data; - } catch (error) { - if (error.response && error.response.data) { - return thunkApi.rejectWithValue(error.response.data); - } - const payload = { - status: false, - msg: error.message ? error.message : "Unknown error", - }; - return thunkApi.rejectWithValue(payload); - } - } + "pageSpeedMonitors/updatePageSpeed", + async (data, thunkApi) => { + try { + const { authToken, monitor } = data; + const updatedFields = { + name: monitor.name, + description: monitor.description, + interval: monitor.interval, + // notifications: monitor.notifications, + }; + const res = await networkService.updateMonitor({ + authToken: authToken, + monitorId: monitor._id, + updatedFields: updatedFields, + }); + return res.data; + } catch (error) { + if (error.response && error.response.data) { + return thunkApi.rejectWithValue(error.response.data); + } + const payload = { + status: false, + msg: error.message ? error.message : "Unknown error", + }; + return thunkApi.rejectWithValue(payload); + } + } ); export const deletePageSpeed = createAsyncThunk( - "pageSpeedMonitors/deletePageSpeed", - async (data, thunkApi) => { - try { - const { authToken, monitor } = data; - const res = await networkService.deleteMonitorById({ - authToken: authToken, - monitorId: monitor._id, - }); - return res.data; - } catch (error) { - if (error.response && error.response.data) { - return thunkApi.rejectWithValue(error.response.data); - } - const payload = { - status: false, - msg: error.message ? error.message : "Unknown error", - }; - return thunkApi.rejectWithValue(payload); - } - } + "pageSpeedMonitors/deletePageSpeed", + async (data, thunkApi) => { + try { + const { authToken, monitor } = data; + const res = await networkService.deleteMonitorById({ + authToken: authToken, + monitorId: monitor._id, + }); + return res.data; + } catch (error) { + if (error.response && error.response.data) { + return thunkApi.rejectWithValue(error.response.data); + } + const payload = { + status: false, + msg: error.message ? error.message : "Unknown error", + }; + return thunkApi.rejectWithValue(payload); + } + } ); export const pausePageSpeed = createAsyncThunk( - "pageSpeedMonitors/pausePageSpeed", - async (data, thunkApi) => { - try { - const { authToken, monitorId } = data; - const res = await networkService.pauseMonitorById({ - authToken: authToken, - monitorId: monitorId, - }); - return res.data; - } catch (error) { - if (error.response && error.response.data) { - return thunkApi.rejectWithValue(error.response.data); - } - const payload = { - status: false, - msg: error.message ? error.message : "Unknown error", - }; - return thunkApi.rejectWithValue(payload); - } - } + "pageSpeedMonitors/pausePageSpeed", + async (data, thunkApi) => { + try { + const { authToken, monitorId } = data; + const res = await networkService.pauseMonitorById({ + authToken: authToken, + monitorId: monitorId, + }); + return res.data; + } catch (error) { + if (error.response && error.response.data) { + return thunkApi.rejectWithValue(error.response.data); + } + const payload = { + status: false, + msg: error.message ? error.message : "Unknown error", + }; + return thunkApi.rejectWithValue(payload); + } + } ); const pageSpeedMonitorSlice = createSlice({ - name: "pageSpeedMonitor", - initialState, - reducers: { - clearMonitorState: (state) => { - state.isLoading = false; - state.monitorsSummary = []; - state.success = null; - state.msg = null; - }, - }, - extraReducers: (builder) => { - builder - // ***************************************************** - // Monitors by teamId - // ***************************************************** + name: "pageSpeedMonitor", + initialState, + reducers: { + clearMonitorState: (state) => { + state.isLoading = false; + state.monitorsSummary = []; + state.success = null; + state.msg = null; + }, + }, + extraReducers: (builder) => { + builder + // ***************************************************** + // Monitors by teamId + // ***************************************************** - .addCase(getPageSpeedByTeamId.pending, (state) => { - state.isLoading = true; - }) - .addCase(getPageSpeedByTeamId.fulfilled, (state, action) => { - state.isLoading = false; - state.success = action.payload.msg; - state.monitorsSummary = action.payload.data; - }) - .addCase(getPageSpeedByTeamId.rejected, (state, action) => { - state.isLoading = false; - state.success = false; - state.msg = action.payload - ? action.payload.msg - : "Getting page speed monitors failed"; - }) + .addCase(getPageSpeedByTeamId.pending, (state) => { + state.isLoading = true; + }) + .addCase(getPageSpeedByTeamId.fulfilled, (state, action) => { + state.isLoading = false; + state.success = action.payload.msg; + state.monitorsSummary = action.payload.data; + }) + .addCase(getPageSpeedByTeamId.rejected, (state, action) => { + state.isLoading = false; + state.success = false; + state.msg = action.payload + ? action.payload.msg + : "Getting page speed monitors failed"; + }) - // ***************************************************** - .addCase(getPagespeedMonitorById.pending, (state) => { - state.isLoading = true; - }) - .addCase(getPagespeedMonitorById.fulfilled, (state, action) => { - state.isLoading = false; - state.success = action.payload.success; - state.msg = action.payload.msg; - }) - .addCase(getPagespeedMonitorById.rejected, (state, action) => { - state.isLoading = false; - state.success = false; - state.msg = action.payload - ? action.payload.msg - : "Failed to get pagespeed monitor"; - }) + // ***************************************************** + .addCase(getPagespeedMonitorById.pending, (state) => { + state.isLoading = true; + }) + .addCase(getPagespeedMonitorById.fulfilled, (state, action) => { + state.isLoading = false; + state.success = action.payload.success; + state.msg = action.payload.msg; + }) + .addCase(getPagespeedMonitorById.rejected, (state, action) => { + state.isLoading = false; + state.success = false; + state.msg = action.payload + ? action.payload.msg + : "Failed to get pagespeed monitor"; + }) - // ***************************************************** - // Create Monitor - // ***************************************************** - .addCase(createPageSpeed.pending, (state) => { - state.isLoading = true; - }) - .addCase(createPageSpeed.fulfilled, (state, action) => { - state.isLoading = false; - state.success = action.payload.success; - state.msg = action.payload.msg; - }) - .addCase(createPageSpeed.rejected, (state, action) => { - state.isLoading = false; - state.success = false; - state.msg = action.payload - ? action.payload.msg - : "Failed to create page speed monitor"; - }) + // ***************************************************** + // Create Monitor + // ***************************************************** + .addCase(createPageSpeed.pending, (state) => { + state.isLoading = true; + }) + .addCase(createPageSpeed.fulfilled, (state, action) => { + state.isLoading = false; + state.success = action.payload.success; + state.msg = action.payload.msg; + }) + .addCase(createPageSpeed.rejected, (state, action) => { + state.isLoading = false; + state.success = false; + state.msg = action.payload + ? action.payload.msg + : "Failed to create page speed monitor"; + }) + // ***************************************************** + // Resolve Endpoint + // ***************************************************** + .addCase(checkEndpointResolution.pending, (state) => { + state.isLoading = true; + }) + .addCase(checkEndpointResolution.fulfilled, (state, action) => { + state.isLoading = false; + state.success = action.payload.success; + state.msg = action.payload.msg; + }) + .addCase(checkEndpointResolution.rejected, (state, action) => { + state.isLoading = false; + state.success = false; + state.msg = action.payload + ? action.payload.msg + : "Failed to check endpoint resolution"; + }) + // ***************************************************** + // Update Monitor + // ***************************************************** + .addCase(updatePageSpeed.pending, (state) => { + state.isLoading = true; + }) + .addCase(updatePageSpeed.fulfilled, (state, action) => { + state.isLoading = false; + state.success = action.payload.success; + state.msg = action.payload.msg; + }) + .addCase(updatePageSpeed.rejected, (state, action) => { + state.isLoading = false; + state.success = false; + state.msg = action.payload + ? action.payload.msg + : "Failed to update page speed monitor"; + }) - // ***************************************************** - // Update Monitor - // ***************************************************** - .addCase(updatePageSpeed.pending, (state) => { - state.isLoading = true; - }) - .addCase(updatePageSpeed.fulfilled, (state, action) => { - state.isLoading = false; - state.success = action.payload.success; - state.msg = action.payload.msg; - }) - .addCase(updatePageSpeed.rejected, (state, action) => { - state.isLoading = false; - state.success = false; - state.msg = action.payload - ? action.payload.msg - : "Failed to update page speed monitor"; - }) - - // ***************************************************** - // Delete Monitor - // ***************************************************** - .addCase(deletePageSpeed.pending, (state) => { - state.isLoading = true; - }) - .addCase(deletePageSpeed.fulfilled, (state, action) => { - state.isLoading = false; - state.success = action.payload.success; - state.msg = action.payload.msg; - }) - .addCase(deletePageSpeed.rejected, (state, action) => { - state.isLoading = false; - state.success = false; - state.msg = action.payload - ? action.payload.msg - : "Failed to delete page speed monitor"; - }) - // ***************************************************** - // Pause Monitor - // ***************************************************** - .addCase(pausePageSpeed.pending, (state) => { - state.isLoading = true; - }) - .addCase(pausePageSpeed.fulfilled, (state, action) => { - state.isLoading = false; - state.success = action.payload.success; - state.msg = action.payload.msg; - }) - .addCase(pausePageSpeed.rejected, (state, action) => { - state.isLoading = false; - state.success = false; - state.msg = action.payload - ? action.payload.msg - : "Failed to pause page speed monitor"; - }); - }, + // ***************************************************** + // Delete Monitor + // ***************************************************** + .addCase(deletePageSpeed.pending, (state) => { + state.isLoading = true; + }) + .addCase(deletePageSpeed.fulfilled, (state, action) => { + state.isLoading = false; + state.success = action.payload.success; + state.msg = action.payload.msg; + }) + .addCase(deletePageSpeed.rejected, (state, action) => { + state.isLoading = false; + state.success = false; + state.msg = action.payload + ? action.payload.msg + : "Failed to delete page speed monitor"; + }) + // ***************************************************** + // Pause Monitor + // ***************************************************** + .addCase(pausePageSpeed.pending, (state) => { + state.isLoading = true; + }) + .addCase(pausePageSpeed.fulfilled, (state, action) => { + state.isLoading = false; + state.success = action.payload.success; + state.msg = action.payload.msg; + }) + .addCase(pausePageSpeed.rejected, (state, action) => { + state.isLoading = false; + state.success = false; + state.msg = action.payload + ? action.payload.msg + : "Failed to pause page speed monitor"; + }); + }, }); export const { setMonitors, clearMonitorState } = pageSpeedMonitorSlice.actions; diff --git a/Client/src/Features/Settings/settingsSlice.js b/Client/src/Features/Settings/settingsSlice.js index 55509fce7..283a8502d 100644 --- a/Client/src/Features/Settings/settingsSlice.js +++ b/Client/src/Features/Settings/settingsSlice.js @@ -2,116 +2,114 @@ import { networkService } from "../../main"; import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"; const initialState = { - isLoading: false, - apiBaseUrl: "", - logLevel: "debug", + isLoading: false, + apiBaseUrl: "", + logLevel: "debug", }; export const getAppSettings = createAsyncThunk( - "settings/getSettings", - async (data, thunkApi) => { - try { - const res = await networkService.getAppSettings({ - authToken: data.authToken, - }); - return res.data; - } catch (error) { - if (error.response.data) { - return thunkApi.rejectWithValue(error.response.data); - } - const payload = { - status: false, - msg: error.message ? error.message : "Unknown error", - }; - return thunkApi.rejectWithValue(payload); - } - } + "settings/getSettings", + async (data, thunkApi) => { + try { + const res = await networkService.getAppSettings({ + authToken: data.authToken, + }); + return res.data; + } catch (error) { + if (error.response.data) { + return thunkApi.rejectWithValue(error.response.data); + } + const payload = { + status: false, + msg: error.message ? error.message : "Unknown error", + }; + return thunkApi.rejectWithValue(payload); + } + } ); export const updateAppSettings = createAsyncThunk( - "settings/updateSettings", - async ({ settings, authToken }, thunkApi) => { - networkService.setBaseUrl(settings.apiBaseUrl); - try { - const parsedSettings = { - apiBaseUrl: settings.apiBaseUrl, - logLevel: settings.logLevel, - clientHost: settings.clientHost, - jwtSecret: settings.jwtSecret, - dbType: settings.dbType, - dbConnectionString: settings.dbConnectionString, - redisHost: settings.redisHost, - redisPort: settings.redisPort, - jwtTTL: settings.jwtTTL, - pagespeedApiKey: settings.pagespeedApiKey, - systemEmailHost: settings.systemEmailHost, - systemEmailPort: settings.systemEmailPort, - systemEmailAddress: settings.systemEmailAddress, - systemEmailPassword: settings.systemEmailPassword, - }; - const res = await networkService.updateAppSettings({ - settings: parsedSettings, - authToken, - }); - return res.data; - } catch (error) { - if (error.response && error.response.data) { - return thunkApi.rejectWithValue(error.response.data); - } - const payload = { - status: false, - msg: error.message ? error.message : "Unknown error", - }; - return thunkApi.rejectWithValue(payload); - } - } + "settings/updateSettings", + async ({ settings, authToken }, thunkApi) => { + networkService.setBaseUrl(settings.apiBaseUrl); + try { + const parsedSettings = { + apiBaseUrl: settings.apiBaseUrl, + logLevel: settings.logLevel, + clientHost: settings.clientHost, + jwtSecret: settings.jwtSecret, + dbType: settings.dbType, + dbConnectionString: settings.dbConnectionString, + redisHost: settings.redisHost, + redisPort: settings.redisPort, + jwtTTL: settings.jwtTTL, + pagespeedApiKey: settings.pagespeedApiKey, + systemEmailHost: settings.systemEmailHost, + systemEmailPort: settings.systemEmailPort, + systemEmailAddress: settings.systemEmailAddress, + systemEmailPassword: settings.systemEmailPassword, + }; + const res = await networkService.updateAppSettings({ + settings: parsedSettings, + authToken, + }); + return res.data; + } catch (error) { + if (error.response && error.response.data) { + return thunkApi.rejectWithValue(error.response.data); + } + const payload = { + status: false, + msg: error.message ? error.message : "Unknown error", + }; + return thunkApi.rejectWithValue(payload); + } + } ); const handleGetSettingsFulfilled = (state, action) => { - state.isLoading = false; - state.success = action.payload.success; - state.msg = action.payload.msg; - state.apiBaseUrl = action.payload.data.apiBaseUrl; - state.logLevel = action.payload.data.logLevel; + state.isLoading = false; + state.success = action.payload.success; + state.msg = action.payload.msg; + state.apiBaseUrl = action.payload.data.apiBaseUrl; + state.logLevel = action.payload.data.logLevel; }; const handleGetSettingsRejected = (state, action) => { - state.isLoading = false; - state.success = false; - state.msg = action.payload ? action.payload.msg : "Failed to get settings."; + state.isLoading = false; + state.success = false; + state.msg = action.payload ? action.payload.msg : "Failed to get settings."; }; const handleUpdateSettingsFulfilled = (state, action) => { - state.isLoading = false; - state.success = action.payload.success; - state.msg = action.payload.msg; - state.apiBaseUrl = action.payload.data.apiBaseUrl; - state.logLevel = action.payload.data.logLevel; + state.isLoading = false; + state.success = action.payload.success; + state.msg = action.payload.msg; + state.apiBaseUrl = action.payload.data.apiBaseUrl; + state.logLevel = action.payload.data.logLevel; }; const handleUpdateSettingsRejected = (state, action) => { - state.isLoading = false; - state.success = false; - state.msg = action.payload - ? action.payload.msg - : "Failed to update settings."; + state.isLoading = false; + state.success = false; + state.msg = action.payload ? action.payload.msg : "Failed to update settings."; }; const settingsSlice = createSlice({ - name: "settings", - initialState, - extraReducers: (builder) => { - builder - .addCase(getAppSettings.pending, (state) => { - state.isLoading = true; - }) - .addCase(getAppSettings.fulfilled, handleGetSettingsFulfilled) - .addCase(getAppSettings.rejected, handleGetSettingsRejected); + name: "settings", + initialState, + extraReducers: (builder) => { + builder + .addCase(getAppSettings.pending, (state) => { + state.isLoading = true; + }) + .addCase(getAppSettings.fulfilled, handleGetSettingsFulfilled) + .addCase(getAppSettings.rejected, handleGetSettingsRejected); - builder - .addCase(updateAppSettings.pending, (state) => { - state.isLoading = true; - }) - .addCase(updateAppSettings.fulfilled, handleUpdateSettingsFulfilled) - .addCase(updateAppSettings.rejected, handleUpdateSettingsRejected); - }, + builder + .addCase(updateAppSettings.pending, (state) => { + state.isLoading = true; + }) + .addCase(updateAppSettings.fulfilled, handleUpdateSettingsFulfilled) + .addCase(updateAppSettings.rejected, handleUpdateSettingsRejected); + }, }); export default settingsSlice.reducer; diff --git a/Client/src/Features/UI/uiSlice.js b/Client/src/Features/UI/uiSlice.js index a70c31041..0a938391a 100644 --- a/Client/src/Features/UI/uiSlice.js +++ b/Client/src/Features/UI/uiSlice.js @@ -3,54 +3,49 @@ import { createSlice } from "@reduxjs/toolkit"; // Initial state for UI settings. // Add more settings as needed (e.g., theme preferences, user settings) const initialState = { - monitors: { - rowsPerPage: 10, - }, - team: { - rowsPerPage: 5, - }, - maintenance: { - rowsPerPage: 5, - }, - sidebar: { - collapsed: false, - }, - mode: "light", - greeting: { index: 0, lastUpdate: null }, - timezone: "America/Toronto", + monitors: { + rowsPerPage: 10, + }, + team: { + rowsPerPage: 5, + }, + maintenance: { + rowsPerPage: 5, + }, + sidebar: { + collapsed: false, + }, + mode: "light", + greeting: { index: 0, lastUpdate: null }, + timezone: "America/Toronto", }; const uiSlice = createSlice({ - name: "ui", - initialState, - reducers: { - setRowsPerPage: (state, action) => { - const { table, value } = action.payload; - if (state[table]) { - state[table].rowsPerPage = value; - } - }, - toggleSidebar: (state) => { - state.sidebar.collapsed = !state.sidebar.collapsed; - }, - setMode: (state, action) => { - state.mode = action.payload; - }, - setGreeting(state, action) { - state.greeting.index = action.payload.index; - state.greeting.lastUpdate = action.payload.lastUpdate; - }, - setTimezone(state, action) { - state.timezone = action.payload.timezone; - }, - }, + name: "ui", + initialState, + reducers: { + setRowsPerPage: (state, action) => { + const { table, value } = action.payload; + if (state[table]) { + state[table].rowsPerPage = value; + } + }, + toggleSidebar: (state) => { + state.sidebar.collapsed = !state.sidebar.collapsed; + }, + setMode: (state, action) => { + state.mode = action.payload; + }, + setGreeting(state, action) { + state.greeting.index = action.payload.index; + state.greeting.lastUpdate = action.payload.lastUpdate; + }, + setTimezone(state, action) { + state.timezone = action.payload.timezone; + }, + }, }); export default uiSlice.reducer; -export const { - setRowsPerPage, - toggleSidebar, - setMode, - setGreeting, - setTimezone, -} = uiSlice.actions; +export const { setRowsPerPage, toggleSidebar, setMode, setGreeting, setTimezone } = + uiSlice.actions; diff --git a/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js b/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js index f6c3cb10a..601dc016a 100644 --- a/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js +++ b/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js @@ -2,405 +2,442 @@ import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"; import { jwtDecode } from "jwt-decode"; import { networkService } from "../../main"; const initialState = { - isLoading: false, - monitorsSummary: [], - success: null, - msg: null, + isLoading: false, + monitorsSummary: [], + success: null, + msg: null, }; export const createUptimeMonitor = createAsyncThunk( - "monitors/createMonitor", - async (data, thunkApi) => { - try { - const { authToken, monitor } = data; - const res = await networkService.createMonitor({ - authToken: authToken, - monitor: monitor, - }); - return res.data; - } catch (error) { - if (error.response && error.response.data) { - return thunkApi.rejectWithValue(error.response.data); - } - const payload = { - status: false, - msg: error.message ? error.message : "Unknown error", - }; - return thunkApi.rejectWithValue(payload); - } - } + "monitors/createMonitor", + async (data, thunkApi) => { + try { + const { authToken, monitor } = data; + const res = await networkService.createMonitor({ + authToken: authToken, + monitor: monitor, + }); + return res.data; + } catch (error) { + if (error.response && error.response.data) { + return thunkApi.rejectWithValue(error.response.data); + } + const payload = { + status: false, + msg: error.message ? error.message : "Unknown error", + }; + return thunkApi.rejectWithValue(payload); + } + } ); +export const checkEndpointResolution = createAsyncThunk( + "monitors/checkEndpoint", + async (data, thunkApi) => { + try { + const { authToken, monitorURL } = data; + + const res = await networkService.checkEndpointResolution({ + authToken: authToken, + monitorURL: monitorURL, + }) + return res.data; + } catch (error) { + if (error.response && error.response.data) { + return thunkApi.rejectWithValue(error.response.data); + } + const payload = { + status: false, + msg: error.message ? error.message : "Unknown error", + }; + return thunkApi.rejectWithValue(payload); + } + } +) + export const getUptimeMonitorById = createAsyncThunk( - "monitors/getMonitorById", - async (data, thunkApi) => { - try { - const { authToken, monitorId } = data; - const res = await networkService.getMonitorById({ - authToken: authToken, - monitorId: monitorId, - }); - return res.data; - } catch (error) { - if (error.response && error.response.data) { - return thunkApi.rejectWithValue(error.response.data); - } - const payload = { - status: false, - msg: error.message ? error.message : "Unknown error", - }; - return thunkApi.rejectWithValue(payload); - } - } + "monitors/getMonitorById", + async (data, thunkApi) => { + try { + const { authToken, monitorId } = data; + const res = await networkService.getMonitorById({ + authToken: authToken, + monitorId: monitorId, + }); + return res.data; + } catch (error) { + if (error.response && error.response.data) { + return thunkApi.rejectWithValue(error.response.data); + } + const payload = { + status: false, + msg: error.message ? error.message : "Unknown error", + }; + return thunkApi.rejectWithValue(payload); + } + } ); export const getUptimeMonitorsByTeamId = createAsyncThunk( - "monitors/getMonitorsByTeamId", - async (token, thunkApi) => { - const user = jwtDecode(token); - try { - const res = await networkService.getMonitorsAndSummaryByTeamId({ - authToken: token, - teamId: user.teamId, - types: ["http", "ping"], - }); - return res.data; - } catch (error) { - if (error.response && error.response.data) { - return thunkApi.rejectWithValue(error.response.data); - } - const payload = { - status: false, - msg: error.message ? error.message : "Unknown error", - }; - return thunkApi.rejectWithValue(payload); - } - } + "monitors/getMonitorsByTeamId", + async (token, thunkApi) => { + const user = jwtDecode(token); + try { + const res = await networkService.getMonitorsAndSummaryByTeamId({ + authToken: token, + teamId: user.teamId, + types: ["http", "ping"], + }); + return res.data; + } catch (error) { + if (error.response && error.response.data) { + return thunkApi.rejectWithValue(error.response.data); + } + const payload = { + status: false, + msg: error.message ? error.message : "Unknown error", + }; + return thunkApi.rejectWithValue(payload); + } + } ); export const updateUptimeMonitor = createAsyncThunk( - "monitors/updateMonitor", - async (data, thunkApi) => { - try { - const { authToken, monitor } = data; - const updatedFields = { - name: monitor.name, - description: monitor.description, - interval: monitor.interval, - notifications: monitor.notifications, - }; - const res = await networkService.updateMonitor({ - authToken: authToken, - monitorId: monitor._id, - updatedFields: updatedFields, - }); - return res.data; - } catch (error) { - if (error.response && error.response.data) { - return thunkApi.rejectWithValue(error.response.data); - } - const payload = { - status: false, - msg: error.message ? error.message : "Unknown error", - }; - return thunkApi.rejectWithValue(payload); - } - } + "monitors/updateMonitor", + async (data, thunkApi) => { + try { + const { authToken, monitor } = data; + const updatedFields = { + name: monitor.name, + description: monitor.description, + interval: monitor.interval, + notifications: monitor.notifications, + }; + const res = await networkService.updateMonitor({ + authToken: authToken, + monitorId: monitor._id, + updatedFields: updatedFields, + }); + return res.data; + } catch (error) { + if (error.response && error.response.data) { + return thunkApi.rejectWithValue(error.response.data); + } + const payload = { + status: false, + msg: error.message ? error.message : "Unknown error", + }; + return thunkApi.rejectWithValue(payload); + } + } ); export const deleteUptimeMonitor = createAsyncThunk( - "monitors/deleteMonitor", - async (data, thunkApi) => { - try { - const { authToken, monitor } = data; - const res = await networkService.deleteMonitorById({ - authToken: authToken, - monitorId: monitor._id, - }); - return res.data; - } catch (error) { - if (error.response && error.response.data) { - return thunkApi.rejectWithValue(error.response.data); - } - const payload = { - status: false, - msg: error.message ? error.message : "Unknown error", - }; - return thunkApi.rejectWithValue(payload); - } - } + "monitors/deleteMonitor", + async (data, thunkApi) => { + try { + const { authToken, monitor } = data; + const res = await networkService.deleteMonitorById({ + authToken: authToken, + monitorId: monitor._id, + }); + return res.data; + } catch (error) { + if (error.response && error.response.data) { + return thunkApi.rejectWithValue(error.response.data); + } + const payload = { + status: false, + msg: error.message ? error.message : "Unknown error", + }; + return thunkApi.rejectWithValue(payload); + } + } ); export const pauseUptimeMonitor = createAsyncThunk( - "monitors/pauseMonitor", - async (data, thunkApi) => { - try { - const { authToken, monitorId } = data; - const res = await networkService.pauseMonitorById({ - authToken: authToken, - monitorId: monitorId, - }); - return res.data; - } catch (error) { - if (error.response && error.response.data) { - return thunkApi.rejectWithValue(error.response.data); - } - const payload = { - status: false, - msg: error.message ? error.message : "Unknown error", - }; - return thunkApi.rejectWithValue(payload); - } - } + "monitors/pauseMonitor", + async (data, thunkApi) => { + try { + const { authToken, monitorId } = data; + const res = await networkService.pauseMonitorById({ + authToken: authToken, + monitorId: monitorId, + }); + return res.data; + } catch (error) { + if (error.response && error.response.data) { + return thunkApi.rejectWithValue(error.response.data); + } + const payload = { + status: false, + msg: error.message ? error.message : "Unknown error", + }; + return thunkApi.rejectWithValue(payload); + } + } ); export const deleteMonitorChecksByTeamId = createAsyncThunk( - "monitors/deleteChecksByTeamId", - async (data, thunkApi) => { - try { - const { authToken, teamId } = data; - const res = await networkService.deleteChecksByTeamId({ - authToken: authToken, - teamId: teamId, - }); - return res.data; - } catch (error) { - if (error.response && error.response.data) { - return thunkApi.rejectWithValue(error.response.data); - } - const payload = { - status: false, - msg: error.message ? error.message : "Unknown error", - }; - return thunkApi.rejectWithValue(payload); - } - } + "monitors/deleteChecksByTeamId", + async (data, thunkApi) => { + try { + const { authToken, teamId } = data; + const res = await networkService.deleteChecksByTeamId({ + authToken: authToken, + teamId: teamId, + }); + return res.data; + } catch (error) { + if (error.response && error.response.data) { + return thunkApi.rejectWithValue(error.response.data); + } + const payload = { + status: false, + msg: error.message ? error.message : "Unknown error", + }; + return thunkApi.rejectWithValue(payload); + } + } ); export const addDemoMonitors = createAsyncThunk( - "monitors/addDemoMonitors", - async (data, thunkApi) => { - try { - const { authToken } = data; - const res = await networkService.addDemoMonitors({ - authToken: authToken, - }); - return res.data; - } catch (error) { - if (error.response && error.response.data) { - return thunkApi.rejectWithValue(error.response.data); - } - const payload = { - status: false, - msg: error.message ? error.message : "Unknown error", - }; - return thunkApi.rejectWithValue(payload); - } - } + "monitors/addDemoMonitors", + async (data, thunkApi) => { + try { + const { authToken } = data; + const res = await networkService.addDemoMonitors({ + authToken: authToken, + }); + return res.data; + } catch (error) { + if (error.response && error.response.data) { + return thunkApi.rejectWithValue(error.response.data); + } + const payload = { + status: false, + msg: error.message ? error.message : "Unknown error", + }; + return thunkApi.rejectWithValue(payload); + } + } ); export const deleteAllMonitors = createAsyncThunk( - "monitors/deleteAllMonitors", - async (data, thunkApi) => { - try { - const { authToken } = data; - const res = await networkService.deleteAllMonitors({ - authToken: authToken, - }); - return res.data; - } catch (error) { - if (error.response && error.response.data) { - return thunkApi.rejectWithValue(error.response.data); - } - const payload = { - status: false, - msg: error.message ? error.message : "Unknown error", - }; - return thunkApi.rejectWithValue(payload); - } - } + "monitors/deleteAllMonitors", + async (data, thunkApi) => { + try { + const { authToken } = data; + const res = await networkService.deleteAllMonitors({ + authToken: authToken, + }); + return res.data; + } catch (error) { + if (error.response && error.response.data) { + return thunkApi.rejectWithValue(error.response.data); + } + const payload = { + status: false, + msg: error.message ? error.message : "Unknown error", + }; + return thunkApi.rejectWithValue(payload); + } + } ); const uptimeMonitorsSlice = createSlice({ - name: "uptimeMonitors", - initialState, - reducers: { - clearUptimeMonitorState: (state) => { - state.isLoading = false; - state.monitorsSummary = []; - state.success = null; - state.msg = null; - }, - }, - extraReducers: (builder) => { - builder - // ***************************************************** - // Monitors by teamId - // ***************************************************** + name: "uptimeMonitors", + initialState, + reducers: { + clearUptimeMonitorState: (state) => { + state.isLoading = false; + state.monitorsSummary = []; + state.success = null; + state.msg = null; + }, + }, + extraReducers: (builder) => { + builder + // ***************************************************** + // Monitors by teamId + // ***************************************************** - .addCase(getUptimeMonitorsByTeamId.pending, (state) => { - state.isLoading = true; - }) - .addCase(getUptimeMonitorsByTeamId.fulfilled, (state, action) => { - state.isLoading = false; - state.success = action.payload.msg; - state.monitorsSummary = action.payload.data; - }) - .addCase(getUptimeMonitorsByTeamId.rejected, (state, action) => { - state.isLoading = false; - state.success = false; - state.msg = action.payload - ? action.payload.msg - : "Getting uptime monitors failed"; - }) + .addCase(getUptimeMonitorsByTeamId.pending, (state) => { + state.isLoading = true; + }) + .addCase(getUptimeMonitorsByTeamId.fulfilled, (state, action) => { + state.isLoading = false; + state.success = action.payload.msg; + state.monitorsSummary = action.payload.data; + }) + .addCase(getUptimeMonitorsByTeamId.rejected, (state, action) => { + state.isLoading = false; + state.success = false; + state.msg = action.payload + ? action.payload.msg + : "Getting uptime monitors failed"; + }) - // ***************************************************** - // Create Monitor - // ***************************************************** - .addCase(createUptimeMonitor.pending, (state) => { - state.isLoading = true; - }) - .addCase(createUptimeMonitor.fulfilled, (state, action) => { - state.isLoading = false; - state.success = action.payload.success; - state.msg = action.payload.msg; - }) - .addCase(createUptimeMonitor.rejected, (state, action) => { - state.isLoading = false; - state.success = false; - state.msg = action.payload - ? action.payload.msg - : "Failed to create uptime monitor"; - }) - // ***************************************************** - // Get Monitor By Id - // ***************************************************** - .addCase(getUptimeMonitorById.pending, (state) => { - state.isLoading = true; - }) - .addCase(getUptimeMonitorById.fulfilled, (state, action) => { - state.isLoading = false; - state.success = action.payload.success; - state.msg = action.payload.msg; - }) - .addCase(getUptimeMonitorById.rejected, (state, action) => { - state.isLoading = false; - state.success = false; - state.msg = action.payload - ? action.payload.msg - : "Failed to get uptime monitor"; - }) - // ***************************************************** - // update Monitor - // ***************************************************** - .addCase(updateUptimeMonitor.pending, (state) => { - state.isLoading = true; - }) - .addCase(updateUptimeMonitor.fulfilled, (state, action) => { - state.isLoading = false; - state.success = action.payload.success; - state.msg = action.payload.msg; - }) - .addCase(updateUptimeMonitor.rejected, (state, action) => { - state.isLoading = false; - state.success = false; - state.msg = action.payload - ? action.payload.msg - : "Failed to update uptime monitor"; - }) + // ***************************************************** + // Create Monitor + // ***************************************************** + .addCase(createUptimeMonitor.pending, (state) => { + state.isLoading = true; + }) + .addCase(createUptimeMonitor.fulfilled, (state, action) => { + state.isLoading = false; + state.success = action.payload.success; + state.msg = action.payload.msg; + }) + .addCase(createUptimeMonitor.rejected, (state, action) => { + state.isLoading = false; + state.success = false; + state.msg = action.payload + ? action.payload.msg + : "Failed to create uptime monitor"; + }) + // ***************************************************** + // Resolve Endpoint + // ***************************************************** + .addCase(checkEndpointResolution.pending, (state) => { + state.isLoading = true; + }) + .addCase(checkEndpointResolution.fulfilled, (state, action) => { + state.isLoading = false; + state.success = action.payload.success; + state.msg = action.payload.msg; + }) + .addCase(checkEndpointResolution.rejected, (state, action) => { + state.isLoading = false; + state.success = false; + state.msg = action.payload + ? action.payload.msg + : "Failed to check endpoint resolution"; + }) + // ***************************************************** + // Get Monitor By Id + // ***************************************************** + .addCase(getUptimeMonitorById.pending, (state) => { + state.isLoading = true; + }) + .addCase(getUptimeMonitorById.fulfilled, (state, action) => { + state.isLoading = false; + state.success = action.payload.success; + state.msg = action.payload.msg; + }) + .addCase(getUptimeMonitorById.rejected, (state, action) => { + state.isLoading = false; + state.success = false; + state.msg = action.payload ? action.payload.msg : "Failed to get uptime monitor"; + }) + // ***************************************************** + // update Monitor + // ***************************************************** + .addCase(updateUptimeMonitor.pending, (state) => { + state.isLoading = true; + }) + .addCase(updateUptimeMonitor.fulfilled, (state, action) => { + state.isLoading = false; + state.success = action.payload.success; + state.msg = action.payload.msg; + }) + .addCase(updateUptimeMonitor.rejected, (state, action) => { + state.isLoading = false; + state.success = false; + state.msg = action.payload + ? action.payload.msg + : "Failed to update uptime monitor"; + }) - // ***************************************************** - // Delete Monitor - // ***************************************************** - .addCase(deleteUptimeMonitor.pending, (state) => { - state.isLoading = true; - }) - .addCase(deleteUptimeMonitor.fulfilled, (state, action) => { - state.isLoading = false; - state.success = action.payload.success; - state.msg = action.payload.msg; - }) - .addCase(deleteUptimeMonitor.rejected, (state, action) => { - state.isLoading = false; - state.success = false; - state.msg = action.payload - ? action.payload.msg - : "Failed to delete uptime monitor"; - }) - // ***************************************************** - // Delete Monitor checks by Team ID - // ***************************************************** - .addCase(deleteMonitorChecksByTeamId.pending, (state) => { - state.isLoading = true; - }) - .addCase(deleteMonitorChecksByTeamId.fulfilled, (state, action) => { - state.isLoading = false; - state.success = action.payload.success; - state.msg = action.payload.msg; - }) - .addCase(deleteMonitorChecksByTeamId.rejected, (state, action) => { - state.isLoading = false; - state.success = false; - state.msg = action.payload - ? action.payload.msg - : "Failed to delete monitor checks"; - }) - // ***************************************************** - // Pause Monitor - // ***************************************************** - .addCase(pauseUptimeMonitor.pending, (state) => { - state.isLoading = true; - }) - .addCase(pauseUptimeMonitor.fulfilled, (state, action) => { - state.isLoading = false; - state.success = action.payload.success; - state.msg = action.payload.msg; - }) - .addCase(pauseUptimeMonitor.rejected, (state, action) => { - state.isLoading = false; - state.success = false; - state.msg = action.payload - ? action.payload.msg - : "Failed to pause uptime monitor"; - }) - // ***************************************************** - // Add Demo Monitors - // ***************************************************** - .addCase(addDemoMonitors.pending, (state) => { - state.isLoading = true; - }) - .addCase(addDemoMonitors.fulfilled, (state, action) => { - state.isLoading = false; - state.success = action.payload.success; - state.msg = action.payload.msg; - }) - .addCase(addDemoMonitors.rejected, (state, action) => { - state.isLoading = false; - state.success = false; - state.msg = action.payload - ? action.payload.msg - : "Failed to add demo uptime monitors"; - }) - // ***************************************************** - // Delete all Monitors - // ***************************************************** - .addCase(deleteAllMonitors.pending, (state) => { - state.isLoading = true; - }) - .addCase(deleteAllMonitors.fulfilled, (state, action) => { - state.isLoading = false; - state.success = action.payload.success; - state.msg = action.payload.msg; - }) - .addCase(deleteAllMonitors.rejected, (state, action) => { - state.isLoading = false; - state.success = false; - state.msg = action.payload - ? action.payload.msg - : "Failed to delete all monitors"; - }); - }, + // ***************************************************** + // Delete Monitor + // ***************************************************** + .addCase(deleteUptimeMonitor.pending, (state) => { + state.isLoading = true; + }) + .addCase(deleteUptimeMonitor.fulfilled, (state, action) => { + state.isLoading = false; + state.success = action.payload.success; + state.msg = action.payload.msg; + }) + .addCase(deleteUptimeMonitor.rejected, (state, action) => { + state.isLoading = false; + state.success = false; + state.msg = action.payload + ? action.payload.msg + : "Failed to delete uptime monitor"; + }) + // ***************************************************** + // Delete Monitor checks by Team ID + // ***************************************************** + .addCase(deleteMonitorChecksByTeamId.pending, (state) => { + state.isLoading = true; + }) + .addCase(deleteMonitorChecksByTeamId.fulfilled, (state, action) => { + state.isLoading = false; + state.success = action.payload.success; + state.msg = action.payload.msg; + }) + .addCase(deleteMonitorChecksByTeamId.rejected, (state, action) => { + state.isLoading = false; + state.success = false; + state.msg = action.payload + ? action.payload.msg + : "Failed to delete monitor checks"; + }) + // ***************************************************** + // Pause Monitor + // ***************************************************** + .addCase(pauseUptimeMonitor.pending, (state) => { + state.isLoading = true; + }) + .addCase(pauseUptimeMonitor.fulfilled, (state, action) => { + state.isLoading = false; + state.success = action.payload.success; + state.msg = action.payload.msg; + }) + .addCase(pauseUptimeMonitor.rejected, (state, action) => { + state.isLoading = false; + state.success = false; + state.msg = action.payload + ? action.payload.msg + : "Failed to pause uptime monitor"; + }) + // ***************************************************** + // Add Demo Monitors + // ***************************************************** + .addCase(addDemoMonitors.pending, (state) => { + state.isLoading = true; + }) + .addCase(addDemoMonitors.fulfilled, (state, action) => { + state.isLoading = false; + state.success = action.payload.success; + state.msg = action.payload.msg; + }) + .addCase(addDemoMonitors.rejected, (state, action) => { + state.isLoading = false; + state.success = false; + state.msg = action.payload + ? action.payload.msg + : "Failed to add demo uptime monitors"; + }) + // ***************************************************** + // Delete all Monitors + // ***************************************************** + .addCase(deleteAllMonitors.pending, (state) => { + state.isLoading = true; + }) + .addCase(deleteAllMonitors.fulfilled, (state, action) => { + state.isLoading = false; + state.success = action.payload.success; + state.msg = action.payload.msg; + }) + .addCase(deleteAllMonitors.rejected, (state, action) => { + state.isLoading = false; + state.success = false; + state.msg = action.payload ? action.payload.msg : "Failed to delete all monitors"; + }); + }, }); -export const { setUptimeMonitors, clearUptimeMonitorState } = - uptimeMonitorsSlice.actions; +export const { setUptimeMonitors, clearUptimeMonitorState } = uptimeMonitorsSlice.actions; export default uptimeMonitorsSlice.reducer; diff --git a/Client/src/HOC/withAdminCheck.jsx b/Client/src/HOC/withAdminCheck.jsx index 097f224e9..fb672f0b5 100644 --- a/Client/src/HOC/withAdminCheck.jsx +++ b/Client/src/HOC/withAdminCheck.jsx @@ -5,28 +5,33 @@ import { logger } from "../Utils/Logger"; import { networkService } from "../main"; const withAdminCheck = (WrappedComponent) => { - const WithAdminCheck = (props) => { - const navigate = useNavigate(); + const WithAdminCheck = (props) => { + const navigate = useNavigate(); - useEffect(() => { - networkService - .doesSuperAdminExist() - .then((response) => { - if (response.data.data === true) { - navigate("/login"); - } - }) - .catch((error) => { - logger.error(error); - }); - }, [navigate]); - return ; - }; - const wrappedComponentName = - WrappedComponent.displayName || WrappedComponent.name || "Component"; - WithAdminCheck.displayName = `WithAdminCheck(${wrappedComponentName})`; + useEffect(() => { + networkService + .doesSuperAdminExist() + .then((response) => { + if (response.data.data === true) { + navigate("/login"); + } + }) + .catch((error) => { + logger.error(error); + }); + }, [navigate]); + return ( + + ); + }; + const wrappedComponentName = + WrappedComponent.displayName || WrappedComponent.name || "Component"; + WithAdminCheck.displayName = `WithAdminCheck(${wrappedComponentName})`; - return WithAdminCheck; + return WithAdminCheck; }; export default withAdminCheck; diff --git a/Client/src/HOC/withAdminProp.jsx b/Client/src/HOC/withAdminProp.jsx index 6910ba5ea..b55893119 100644 --- a/Client/src/HOC/withAdminProp.jsx +++ b/Client/src/HOC/withAdminProp.jsx @@ -1,20 +1,25 @@ import { useSelector } from "react-redux"; const withAdminProp = (WrappedComponent) => { - const WithAdminProp = (props) => { - const { user } = useSelector((state) => state.auth); - const isAdmin = - (user?.role?.includes("admin") ?? false) || - (user?.role?.includes("superadmin") ?? false); + const WithAdminProp = (props) => { + const { user } = useSelector((state) => state.auth); + const isAdmin = + (user?.role?.includes("admin") ?? false) || + (user?.role?.includes("superadmin") ?? false); - return ; - }; + return ( + + ); + }; - const wrappedComponentName = - WrappedComponent.displayName || WrappedComponent.name || "Component"; - WithAdminProp.displayName = `WithAdminProp(${wrappedComponentName})`; + const wrappedComponentName = + WrappedComponent.displayName || WrappedComponent.name || "Component"; + WithAdminProp.displayName = `WithAdminProp(${wrappedComponentName})`; - return WithAdminProp; + return WithAdminProp; }; export default withAdminProp; diff --git a/Client/src/Layouts/HomeLayout/index.css b/Client/src/Layouts/HomeLayout/index.css index d9fe9e06b..1a7ae4ceb 100644 --- a/Client/src/Layouts/HomeLayout/index.css +++ b/Client/src/Layouts/HomeLayout/index.css @@ -1,38 +1,38 @@ .home-layout { - position: relative; - min-height: 100vh; - max-width: 1400px; - margin: 0 auto; - padding: var(--env-var-spacing-2); + position: relative; + min-height: 100vh; + max-width: 1400px; + margin: 0 auto; + padding: var(--env-var-spacing-2); } .home-layout aside { - position: sticky; - top: var(--env-var-spacing-2); - left: 0; + position: sticky; + top: var(--env-var-spacing-2); + left: 0; - height: calc(100vh - var(--env-var-spacing-2) * 2); - max-width: var(--env-var-side-bar-width); + height: calc(100vh - var(--env-var-spacing-2) * 2); + max-width: var(--env-var-side-bar-width); } .home-layout > div { - min-height: calc(100vh - var(--env-var-spacing-2) * 2); - flex: 1; + min-height: calc(100vh - var(--env-var-spacing-2) * 2); + flex: 1; } .home-layout > div:has(> [class*="fallback__"]) .background-pattern-svg { - position: absolute; - top: 0; - left: 50%; - transform: translate(-50%, -50%); - z-index: 0; + position: absolute; + top: 0; + left: 50%; + transform: translate(-50%, -50%); + z-index: 0; - width: 100%; - max-width: 800px; - height: 100%; - max-height: 800px; + width: 100%; + max-width: 800px; + height: 100%; + max-height: 800px; - background-position: center; - background-size: cover; - background-repeat: no-repeat; + background-position: center; + background-size: cover; + background-repeat: no-repeat; } diff --git a/Client/src/Layouts/HomeLayout/index.jsx b/Client/src/Layouts/HomeLayout/index.jsx index 62af75075..0fe787fd6 100644 --- a/Client/src/Layouts/HomeLayout/index.jsx +++ b/Client/src/Layouts/HomeLayout/index.jsx @@ -5,12 +5,16 @@ import { Stack } from "@mui/material"; import "./index.css"; const HomeLayout = () => { - return ( - - - - - ); + return ( + + + + + ); }; export default HomeLayout; diff --git a/Client/src/Pages/Account/index.css b/Client/src/Pages/Account/index.css index 24b47d3e9..a4e8d88f0 100644 --- a/Client/src/Pages/Account/index.css +++ b/Client/src/Pages/Account/index.css @@ -4,35 +4,35 @@ .account button, .account td, .account .MuiSelect-select { - font-size: var(--env-var-font-size-medium); + font-size: var(--env-var-font-size-medium); } .account h1.MuiTypography-root { - font-weight: 600; + font-weight: 600; } .account .MuiTabPanel-root { - padding: 0; - margin-top: 50px; + padding: 0; + margin-top: 50px; } .account button:not(.MuiIconButton-root) { - height: 34px; + height: 34px; } .account .field { - flex: 1; + flex: 1; } #modal-delete-account, #modal-edit-org-name, #modal-invite-member { - font-size: var(--env-var-font-size-large); + font-size: var(--env-var-font-size-large); } .account .MuiStack-root:has(span.MuiTypography-root.input-error) { - position: relative; + position: relative; } .account:not(:has(#modal-invite-member)) span.MuiTypography-root.input-error { - position: absolute; - top: 100%; + position: absolute; + top: 100%; } .account .MuiTableBody-root .MuiTableCell-root { - padding: var(--env-var-spacing-1-plus) var(--env-var-spacing-2); + padding: var(--env-var-spacing-1-plus) var(--env-var-spacing-2); } diff --git a/Client/src/Pages/Account/index.jsx b/Client/src/Pages/Account/index.jsx index 5543b38ee..3874571ad 100644 --- a/Client/src/Pages/Account/index.jsx +++ b/Client/src/Pages/Account/index.jsx @@ -16,78 +16,81 @@ import "./index.css"; */ const Account = ({ open = "profile" }) => { - const theme = useTheme(); - const navigate = useNavigate(); - const tab = open; - const handleTabChange = (event, newTab) => { - navigate(`/account/${newTab}`); - }; - const { user } = useSelector((state) => state.auth); + const theme = useTheme(); + const navigate = useNavigate(); + const tab = open; + const handleTabChange = (event, newTab) => { + navigate(`/account/${newTab}`); + }; + const { user } = useSelector((state) => state.auth); - const requiredRoles = ["superadmin", "admin"]; - let tabList = ["Profile", "Password", "Team"]; - const hideTeams = !requiredRoles.some((role) => user.role.includes(role)); - if (hideTeams) { - tabList = ["Profile", "Password"]; - } + const requiredRoles = ["superadmin", "admin"]; + let tabList = ["Profile", "Password", "Team"]; + const hideTeams = !requiredRoles.some((role) => user.role.includes(role)); + if (hideTeams) { + tabList = ["Profile", "Password"]; + } - // Remove password for demo - if (user.role.includes("demo")) { - tabList = ["Profile"]; - } + // Remove password for demo + if (user.role.includes("demo")) { + tabList = ["Profile"]; + } - return ( - - - - - {tabList.map((label, index) => ( - - ))} - - - - {user.role.includes("superadmin") && } - {!hideTeams && } - - - ); + return ( + + + + + {tabList.map((label, index) => ( + + ))} + + + + {user.role.includes("superadmin") && } + {!hideTeams && } + + + ); }; Account.propTypes = { - open: PropTypes.oneOf(["profile", "password", "team"]), + open: PropTypes.oneOf(["profile", "password", "team"]), }; export default Account; diff --git a/Client/src/Pages/AdvancedSettings/index.jsx b/Client/src/Pages/AdvancedSettings/index.jsx index 6af21cc31..12016889f 100644 --- a/Client/src/Pages/AdvancedSettings/index.jsx +++ b/Client/src/Pages/AdvancedSettings/index.jsx @@ -9,16 +9,14 @@ import PropTypes from "prop-types"; import LoadingButton from "@mui/lab/LoadingButton"; import { ConfigBox } from "../Settings/styled"; import { useNavigate } from "react-router"; -import { - getAppSettings, - updateAppSettings, -} from "../../Features/Settings/settingsSlice"; +import { getAppSettings, updateAppSettings } from "../../Features/Settings/settingsSlice"; import { useState, useEffect } from "react"; import Select from "../../Components/Inputs/Select"; +import { advancedSettingsValidation } from "../../Validation/validation"; +import { buildErrors, hasValidationErrors } from "../../Validation/error"; const AdvancedSettings = ({ isAdmin }) => { const navigate = useNavigate(); - useEffect(() => { if (!isAdmin) { navigate("/"); @@ -42,11 +40,12 @@ const AdvancedSettings = ({ isAdmin }) => { redisPort: "", pagespeedApiKey: "", }); + const [errors, setErrors] = useState({}); useEffect(() => { const getSettings = async () => { const action = await dispatch(getAppSettings({ authToken })); - if (action.payload.success) { + if (action.payload?.success) { setLocalSettings(action.payload.data); } else { createToast({ body: "Failed to get settings" }); @@ -55,32 +54,43 @@ const AdvancedSettings = ({ isAdmin }) => { getSettings(); }, [authToken, dispatch]); - const logItems = [ - { _id: 1, name: "none" }, - { _id: 2, name: "debug" }, - { _id: 3, name: "error" }, - { _id: 4, name: "warn" }, - ]; + const logItems = [ + { _id: 1, name: "none" }, + { _id: 2, name: "debug" }, + { _id: 3, name: "error" }, + { _id: 4, name: "warn" }, + ]; - const logItemLookup = { - none: 1, - debug: 2, - error: 3, - warn: 4, - }; + const logItemLookup = { + none: 1, + debug: 2, + error: 3, + warn: 4, + }; - const handleLogLevel = (e) => { - const id = e.target.value; - const newLogLevel = logItems.find((item) => item._id === id).name; - setLocalSettings({ ...localSettings, logLevel: newLogLevel }); - }; + const handleLogLevel = (e) => { + const id = e.target.value; + const newLogLevel = logItems.find((item) => item._id === id).name; + setLocalSettings({ ...localSettings, logLevel: newLogLevel }); + }; const handleChange = (event) => { const { value, id } = event.target; setLocalSettings({ ...localSettings, [id]: value }); + const { error } = advancedSettingsValidation.validate( + { [id]: value }, + { + abortEarly: false, + } + ); + setErrors((prev) => { + return buildErrors(prev, id, error); + }); }; - const handleSave = async () => { + const handleSave = async () => { + if (hasValidationErrors(localSettings, advancedSettingsValidation, setErrors)) + return; const action = await dispatch( updateAppSettings({ settings: localSettings, authToken }) ); @@ -121,6 +131,7 @@ const AdvancedSettings = ({ isAdmin }) => { label="API URL Host" value={localSettings.apiBaseUrl} onChange={handleChange} + error={errors.apiBaseUrl} /> - - - - - - - - - )} - - ); + return ( + + {isActuallyLoading ? ( + + ) : ( + <> + + + Incidents for + + { - handleFormChange( - "repeat", - getValueById(repeatConfig, event.target.value) - ); - }} - items={repeatConfig} - /> - - Date - - .MuiOutlinedInput-root": { - flexDirection: "row-reverse", - }, - "& input": { - height: 34, - p: 0, - pr: theme.spacing(5), - }, - "& fieldset": { - borderColor: theme.palette.border.dark, - borderRadius: theme.shape.borderRadius, - }, - "&:not(:has(.Mui-disabled)):not(:has(.Mui-error)) .MuiOutlinedInput-root:not(:has(input:focus)):hover fieldset": - { - borderColor: theme.palette.border.dark, - }, - }, - }, - inputAdornment: { sx: { ml: 0, px: 3 } }, - openPickerButton: { - sx: { - py: 0, - mr: 0, - "& path": { - stroke: theme.palette.other.icon, - strokeWidth: 1.1, - }, - "&:hover": { backgroundColor: "transparent" }, - }, - }, - }} - sx={{}} - onChange={(newDate) => { - handleTimeChange("startDate", newDate); - }} - error={errors["startDate"]} - /> - - - - - - - - - Start time - - - All dates and times are in GMT+0 time zone. - - - - - { - handleTimeChange("startTime", newTime); - }} - slotProps={{ - nextIconButton: { sx: { ml: theme.spacing(2) } }, - field: { - sx: { - width: "fit-content", - "& > .MuiOutlinedInput-root": { - flexDirection: "row-reverse", - }, - "& input": { - height: 34, - p: 0, - pl: theme.spacing(5), - }, - "& fieldset": { - borderColor: theme.palette.border.dark, - borderRadius: theme.shape.borderRadius, - }, - "&:not(:has(.Mui-disabled)):not(:has(.Mui-error)) .MuiOutlinedInput-root:not(:has(input:focus)):hover fieldset": - { - borderColor: theme.palette.border.dark, - }, - }, - }, - }} - error={errors["startTime"]} - /> - - - - - - - Duration - - - - { - handleFormChange("duration", event.target.value); - }} - error={errors["duration"]} - /> - { + handleFormChange( + "repeat", + getValueById(repeatConfig, event.target.value) + ); + }} + items={repeatConfig} + /> + + Date + + .MuiOutlinedInput-root": { + flexDirection: "row-reverse", + }, + "& input": { + height: 34, + p: 0, + pr: theme.spacing(5), + }, + "& fieldset": { + borderColor: theme.palette.border.dark, + borderRadius: theme.shape.borderRadius, + }, + "&:not(:has(.Mui-disabled)):not(:has(.Mui-error)) .MuiOutlinedInput-root:not(:has(input:focus)):hover fieldset": + { + borderColor: theme.palette.border.dark, + }, + }, + }, + inputAdornment: { sx: { ml: 0, px: 3 } }, + openPickerButton: { + sx: { + py: 0, + mr: 0, + "& path": { + stroke: theme.palette.other.icon, + strokeWidth: 1.1, + }, + "&:hover": { backgroundColor: "transparent" }, + }, + }, + }} + sx={{}} + onChange={(newDate) => { + handleTimeChange("startDate", newDate); + }} + error={errors["startDate"]} + /> + + + + + + + + + Start time + + All dates and times are in GMT+0 time zone. + + + + { + handleTimeChange("startTime", newTime); + }} + slotProps={{ + nextIconButton: { sx: { ml: theme.spacing(2) } }, + field: { + sx: { + width: "fit-content", + "& > .MuiOutlinedInput-root": { + flexDirection: "row-reverse", + }, + "& input": { + height: 34, + p: 0, + pl: theme.spacing(5), + }, + "& fieldset": { + borderColor: theme.palette.border.dark, + borderRadius: theme.shape.borderRadius, + }, + "&:not(:has(.Mui-disabled)):not(:has(.Mui-error)) .MuiOutlinedInput-root:not(:has(input:focus)):hover fieldset": + { + borderColor: theme.palette.border.dark, + }, + }, + }, + }} + error={errors["startTime"]} + /> + + + + + + + Duration + + + + { + handleFormChange("duration", event.target.value); + }} + error={errors["duration"]} + /> + handleChange(event, "interval")} - items={frequencies} - /> - - - - - Save - - - - - )} - setIsOpen(false)} - title="Do you really want to delete this monitor?" - confirmationBtnLbl="Delete" - confirmationBtnOnClick={handleRemove} - cancelBtnLbl="Cancel" - cancelBtnOnClick={() => setIsOpen(false)} - theme={theme} - isLoading={isLoading} - description="Once deleted, this monitor cannot be retrieved." - > - - - ); + return ( + + {Object.keys(monitor).length === 0 ? ( + + ) : ( + <> + + + + + + {monitor.name} + + + + + + + + + {monitor.url?.replace(/^https?:\/\//, "") || "..."} + + + Editting... + + + + + + {monitor?.isActive ? ( + <> + + Pause + + ) : ( + <> + + Resume + + )} + + setIsOpen(true)} + > + Remove + + + + + + General settings + + Here you can select the URL of the host, together with the type of + monitor. + + + + + + + + + + Incident notifications + + When there is an incident, notify users. + + + + When there is a new incident, + logger.warn("disabled")} + isDisabled={true} + /> + notification.type === "email" + ) || false + } + value={user?.email} + onChange={(event) => handleChange(event)} + /> + logger.warn("disabled")} + isDisabled={true} + /> + {monitor?.notifications?.some( + (notification) => notification.type === "emails" + ) ? ( + + logger.warn("disabled")} + /> + + You can separate multiple emails with a comma + + + ) : ( + "" + )} + + + + + Advanced settings + + + handleChange(event, "interval")} - items={frequencies} - /> - - - - + + + ) : ( + "" + )} + + handleChange(event)} + /> + {errors["type"] ? ( + + + {errors["type"]} + + + ) : ( + "" + )} + + + + + Incident notifications + + When there is an incident, notify users. + + + + When there is a new incident, + logger.warn("disabled")} + isDisabled={true} + /> + notification.type === "email" + )} + value={user?.email} + onChange={(event) => handleChange(event)} + /> + logger.warn("disabled")} + isDisabled={true} + /> + {monitor.notifications.some( + (notification) => notification.type === "emails" + ) ? ( + + logger.warn("disabled")} + /> + + You can separate multiple emails with a comma + + + ) : ( + "" + )} + + + + + Advanced settings + + + handleChange(event, "interval")} - /> - - - - - Save - - - - - )} - setIsOpen(false)} - disablePortal - > - - - Do you really want to delete this monitor? - - - Once deleted, this monitor cannot be retrieved. - - - - - - - - - ); + return ( + + {Object.keys(monitor).length === 0 ? ( + + ) : ( + <> + + + + + + {monitor.name} + + + + + + + + + {monitor.url?.replace(/^https?:\/\//, "") || "..."} + + + Editing... + + + + + + {monitor?.isActive ? ( + <> + + Pause + + ) : ( + <> + + Resume + + )} + + setIsOpen(true)} + sx={{ + ml: theme.spacing(6), + }} + > + Remove + + + + + + General settings + + Here you can select the URL of the host, together with the type of + monitor. + + + .Mui-disabled)": { + backgroundColor: theme.palette.background.accent, + }, + }} + > + + + + + + + Incident notifications + + When there is an incident, notify users. + + + + When there is a new incident, + logger.warn("disabled")} + isDisabled={true} + /> + notification.type === "email" + ) || false + } + value={user?.email} + onChange={(event) => handleChange(event)} + /> + logger.warn("disabled")} + isDisabled={true} + /> + {monitor?.notifications?.some( + (notification) => notification.type === "emails" + ) ? ( + + logger.warn("disabled")} + /> + + You can separate multiple emails with a comma + + + ) : ( + "" + )} + + + + + Advanced settings + + + { - dispatch(setTimezone({ timezone: e.target.value })); - }} - items={timezones} - /> - - - - - Appearance - - Switch between light and dark mode. - - - - - - - {isAdmin && ( - - - History and monitoring - - Define here for how long you want to keep the data. You can also - remove all past data. - - - - - - Clear all stats. This is irreversible. - - - - setIsOpen(deleteStatsMonitorsInitState)} - title="Do you want to clear all stats?" - confirmationBtnLbl="Yes, clear all stats" - confirmationBtnOnClick={handleClearStats} - cancelBtnLbl="Cancel" - cancelBtnOnClick={() => setIsOpen(deleteStatsMonitorsInitState)} - theme={theme} - isLoading={isLoading || authIsLoading || checksIsLoading} - > - - - )} - {isAdmin && ( - - - Demo monitors - - Here you can add and remove demo monitors - - - - - Add demo monitors - - Add demo monitors - - - - Remove all monitors - setIsOpen({...deleteStatsMonitorsInitState, deleteMonitors: true})} - sx={{ mt: theme.spacing(4) }} - > - Remove all monitors - - - - setIsOpen(deleteStatsMonitorsInitState)} - title="Do you want to remove all monitors?" - confirmationBtnLbl="Yes, clear all monitors" - confirmationBtnOnClick={handleDeleteAllMonitors} - cancelBtnLbl="Cancel" - cancelBtnOnClick={() => setIsOpen(deleteStatsMonitorsInitState)} - theme={theme} - isLoading={isLoading || authIsLoading || checksIsLoading} - > - - - )} - {isAdmin && ( - - - Advanced settings - - Click here to modify advanced settings - - - - - - - - - )} - - - About - - - BlueWave Uptime {version} - - Developed by Bluewave Labs. - - - - - - 0} - variant="contained" - color="primary" - sx={{ px: theme.spacing(12), mt: theme.spacing(20) }} - onClick={handleSave} - > - Save - - - - - ); + const handleDeleteAllMonitors = async () => { + try { + const action = await dispatch(deleteAllMonitors({ authToken })); + if (deleteAllMonitors.fulfilled.match(action)) { + createToast({ body: "Successfully deleted all monitors" }); + } else { + createToast({ body: "Failed to add demo monitors" }); + } + } catch (error) { + logger.error(error); + createToast({ Body: "Failed to delete all monitors" }); + } finally { + setIsOpen(deleteStatsMonitorsInitState); + } + }; + + return ( + + + + + General settings + + Display timezone- The timezone of + the dashboard you publicly display. + + + + { + dispatch(setMode(e.target.value)); + }} + items={[ + { _id: "light", name: "Light" }, + { _id: "dark", name: "Dark" }, + ]} + > + + + {isAdmin && ( + + + History and monitoring + + Define here for how long you want to keep the data. You can also remove + all past data. + + + + + + Clear all stats. This is irreversible. + + + + setIsOpen(deleteStatsMonitorsInitState)} + title="Do you want to clear all stats?" + confirmationBtnLbl="Yes, clear all stats" + confirmationBtnOnClick={handleClearStats} + cancelBtnLbl="Cancel" + cancelBtnOnClick={() => setIsOpen(deleteStatsMonitorsInitState)} + theme={theme} + isLoading={isLoading || authIsLoading || checksIsLoading} + > + + )} + {isAdmin && ( + + + Demo monitors + + Here you can add and remove demo monitors + + + + + Add demo monitors + + Add demo monitors + + + + Remove all monitors + + setIsOpen({ ...deleteStatsMonitorsInitState, deleteMonitors: true }) + } + sx={{ mt: theme.spacing(4) }} + > + Remove all monitors + + + + setIsOpen(deleteStatsMonitorsInitState)} + title="Do you want to remove all monitors?" + confirmationBtnLbl="Yes, clear all monitors" + confirmationBtnOnClick={handleDeleteAllMonitors} + cancelBtnLbl="Cancel" + cancelBtnOnClick={() => setIsOpen(deleteStatsMonitorsInitState)} + theme={theme} + isLoading={isLoading || authIsLoading || checksIsLoading} + > + + )} + {isAdmin && ( + + + Advanced settings + + Click here to modify advanced settings + + + + + + + + + )} + + + About + + + BlueWave Uptime {version} + + Developed by Bluewave Labs. + + + + + + 0} + variant="contained" + color="primary" + sx={{ px: theme.spacing(12), mt: theme.spacing(20) }} + onClick={handleSave} + > + Save + + + + + ); }; Settings.propTypes = { - isAdmin: PropTypes.bool, + isAdmin: PropTypes.bool, }; export default Settings; diff --git a/Client/src/Pages/Settings/styled.jsx b/Client/src/Pages/Settings/styled.jsx index 19da4815b..269706a27 100644 --- a/Client/src/Pages/Settings/styled.jsx +++ b/Client/src/Pages/Settings/styled.jsx @@ -1,28 +1,28 @@ import { Stack, styled } from "@mui/material"; export const ConfigBox = styled(Stack)(({ theme }) => ({ - display: "flex", - flexDirection: "row", - justifyContent: "space-between", - gap: theme.spacing(20), - paddingTop: theme.spacing(12), - paddingInline: theme.spacing(15), - paddingBottom: theme.spacing(25), - backgroundColor: theme.palette.background.main, - border: 1, - borderStyle: "solid", - borderColor: theme.palette.border.light, - borderRadius: theme.spacing(2), - "& > div:first-of-type": { - flex: 0.7, - }, - "& > div:last-of-type": { - flex: 1, - }, - "& h1, & h2": { - color: theme.palette.text.secondary, - }, - "& p": { - color: theme.palette.text.tertiary, - }, + display: "flex", + flexDirection: "row", + justifyContent: "space-between", + gap: theme.spacing(20), + paddingTop: theme.spacing(12), + paddingInline: theme.spacing(15), + paddingBottom: theme.spacing(25), + backgroundColor: theme.palette.background.main, + border: 1, + borderStyle: "solid", + borderColor: theme.palette.border.light, + borderRadius: theme.spacing(2), + "& > div:first-of-type": { + flex: 0.7, + }, + "& > div:last-of-type": { + flex: 1, + }, + "& h1, & h2": { + color: theme.palette.text.secondary, + }, + "& p": { + color: theme.palette.text.tertiary, + }, })); diff --git a/Client/src/Pages/Status/index.jsx b/Client/src/Pages/Status/index.jsx index cb759b33b..6827834ac 100644 --- a/Client/src/Pages/Status/index.jsx +++ b/Client/src/Pages/Status/index.jsx @@ -3,33 +3,33 @@ import { useTheme } from "@emotion/react"; import Fallback from "../../Components/Fallback"; const Status = () => { - const theme = useTheme(); + const theme = useTheme(); - return ( - [class*="fallback__"])': { - position: "relative", - border: 1, - borderColor: theme.palette.border.light, - borderRadius: theme.shape.borderRadius, - borderStyle: "dashed", - backgroundColor: theme.palette.background.main, - overflow: "hidden", - }, - }} - > - - - ); + return ( + [class*="fallback__"])': { + position: "relative", + border: 1, + borderColor: theme.palette.border.light, + borderRadius: theme.shape.borderRadius, + borderStyle: "dashed", + backgroundColor: theme.palette.background.main, + overflow: "hidden", + }, + }} + > + + + ); }; export default Status; diff --git a/Client/src/Utils/Logger.js b/Client/src/Utils/Logger.js index a3cb9bc16..177391b99 100644 --- a/Client/src/Utils/Logger.js +++ b/Client/src/Utils/Logger.js @@ -3,54 +3,54 @@ const LOG_LEVEL = import.meta.env.VITE_APP_LOG_LEVEL || "debug"; const NO_OP = () => {}; class Logger { - constructor() { - let logLevel = LOG_LEVEL; - this.unsubscribe = store.subscribe(() => { - const state = store.getState(); - logLevel = state.settings.logLevel || "debug"; - this.updateLogLevel(logLevel); - }); - } + constructor() { + let logLevel = LOG_LEVEL; + this.unsubscribe = store.subscribe(() => { + const state = store.getState(); + logLevel = state.settings.logLevel || "debug"; + this.updateLogLevel(logLevel); + }); + } - updateLogLevel(logLevel) { - if (logLevel === "none") { - this.info = NO_OP; - this.error = NO_OP; - this.warn = NO_OP; - this.log = NO_OP; - return; - } + updateLogLevel(logLevel) { + if (logLevel === "none") { + this.info = NO_OP; + this.error = NO_OP; + this.warn = NO_OP; + this.log = NO_OP; + return; + } - if (logLevel === "error") { - this.error = console.error.bind(console); - this.info = NO_OP; - this.warn = NO_OP; - this.log = NO_OP; - return; - } + if (logLevel === "error") { + this.error = console.error.bind(console); + this.info = NO_OP; + this.warn = NO_OP; + this.log = NO_OP; + return; + } - if (logLevel === "warn") { - this.error = console.error.bind(console); - this.warn = console.warn.bind(console); - this.info = NO_OP; - this.log = NO_OP; - return; - } + if (logLevel === "warn") { + this.error = console.error.bind(console); + this.warn = console.warn.bind(console); + this.info = NO_OP; + this.log = NO_OP; + return; + } - if (logLevel === "info") { - this.error = console.error.bind(console); - this.warn = console.warn.bind(console); - this.info = console.info.bind(console); - this.log = NO_OP; - return; - } - } + if (logLevel === "info") { + this.error = console.error.bind(console); + this.warn = console.warn.bind(console); + this.info = console.info.bind(console); + this.log = NO_OP; + return; + } + } - cleanup() { - if (this.unsubscribe) { - this.unsubscribe(); - } - } + cleanup() { + if (this.unsubscribe) { + this.unsubscribe(); + } + } } export const logger = new Logger(); diff --git a/Client/src/Utils/NetworkService.js b/Client/src/Utils/NetworkService.js index 79e5b701e..5571d40ff 100644 --- a/Client/src/Utils/NetworkService.js +++ b/Client/src/Utils/NetworkService.js @@ -3,91 +3,116 @@ const BASE_URL = import.meta.env.VITE_APP_API_BASE_URL; const FALLBACK_BASE_URL = "http://localhost:5000/api/v1"; import { clearAuthState } from "../Features/Auth/authSlice"; import { clearUptimeMonitorState } from "../Features/UptimeMonitors/uptimeMonitorsSlice"; -import { logger } from "./Logger"; class NetworkService { - constructor(store, dispatch, navigate) { - this.store = store; - this.dispatch = dispatch; - this.navigate = navigate; - let baseURL = BASE_URL; - this.axiosInstance = axios.create(); - this.setBaseUrl(baseURL); - this.unsubscribe = store.subscribe(() => { - const state = store.getState(); - if (BASE_URL !== undefined) { - baseURL = BASE_URL; - } else if (state?.settings?.apiBaseUrl ?? null) { - baseURL = state.settings.apiBaseUrl; - } else { - baseURL = FALLBACK_BASE_URL; - } - this.setBaseUrl(baseURL); - }); - this.axiosInstance.interceptors.response.use( - (response) => response, - (error) => { - if (error.response && error.response.status === 401) { - dispatch(clearAuthState()); - dispatch(clearUptimeMonitorState()); - navigate("/login"); - } - return Promise.reject(error); - } - ); - } + constructor(store, dispatch, navigate) { + this.store = store; + this.dispatch = dispatch; + this.navigate = navigate; + let baseURL = BASE_URL; + this.axiosInstance = axios.create(); + this.setBaseUrl(baseURL); + this.unsubscribe = store.subscribe(() => { + const state = store.getState(); + if (BASE_URL !== undefined) { + baseURL = BASE_URL; + } else if (state?.settings?.apiBaseUrl ?? null) { + baseURL = state.settings.apiBaseUrl; + } else { + baseURL = FALLBACK_BASE_URL; + } + this.setBaseUrl(baseURL); + }); + this.axiosInstance.interceptors.response.use( + (response) => response, + (error) => { + if (error.response && error.response.status === 401) { + dispatch(clearAuthState()); + dispatch(clearUptimeMonitorState()); + navigate("/login"); + } + return Promise.reject(error); + } + ); + } - setBaseUrl = (url) => { - this.axiosInstance.defaults.baseURL = url; - }; + setBaseUrl = (url) => { + this.axiosInstance.defaults.baseURL = url; + }; - cleanup() { - if (this.unsubscribe) { - this.unsubscribe(); - } - } + cleanup() { + if (this.unsubscribe) { + this.unsubscribe(); + } + } + /** + * + * ************************************ + * Create a new monitor + * ************************************ + * + * @async + * @param {Object} config - The configuration object. + * @param {string} config.authToken - The authorization token to be used in the request header. + * @param {string} config.monitorId - The monitor ID to be sent in the param. + * @returns {Promise} The response from the axios GET request. + */ + + async getMonitorById(config) { + return this.axiosInstance.get(`/monitors/${config.monitorId}`, { + headers: { + Authorization: `Bearer ${config.authToken}`, + "Content-Type": "application/json", + }, + }); + } + + /** + * + * ************************************ + * Create a new monitor + * ************************************ + * + * @async + * @param {Object} config - The configuration object. + * @param {string} config.authToken - The authorization token to be used in the request header. + * @param {Object} config.monitor - The monitor object to be sent in the request body. + * @returns {Promise} The response from the axios POST request. + */ + async createMonitor(config) { + return this.axiosInstance.post(`/monitors`, config.monitor, { + headers: { + Authorization: `Bearer ${config.authToken}`, + "Content-Type": "application/json", + }, + }); + } + /** * * ************************************ - * Create a new monitor + * Check the endpoint resolution * ************************************ * * @async * @param {Object} config - The configuration object. * @param {string} config.authToken - The authorization token to be used in the request header. - * @param {string} config.monitorId - The monitor ID to be sent in the param. - * @returns {Promise} The response from the axios GET request. - */ - - async getMonitorById(config) { - return this.axiosInstance.get(`/monitors/${config.monitorId}`, { - headers: { - Authorization: `Bearer ${config.authToken}`, - "Content-Type": "application/json", - }, - }); - } - - /** - * - * ************************************ - * Create a new monitor - * ************************************ - * - * @async - * @param {Object} config - The configuration object. - * @param {string} config.authToken - The authorization token to be used in the request header. - * @param {Object} config.monitor - The monitor object to be sent in the request body. + * @param {Object} config.monitorURL - The monitor url to be sent in the request body. * @returns {Promise} The response from the axios POST request. */ - async createMonitor(config) { - return this.axiosInstance.post(`/monitors`, config.monitor, { - headers: { - Authorization: `Bearer ${config.authToken}`, - "Content-Type": "application/json", - }, - }); - } + async checkEndpointResolution(config) { + const { authToken, monitorURL } = config; + const params = new URLSearchParams(); + + if (monitorURL) params.append("monitorURL", monitorURL); + + return this.axiosInstance.get(`/monitors/resolution/url?${params.toString()}`, { + headers: { + Authorization: `Bearer ${authToken}`, + "Content-Type": "application/json", + } + }) + } /** * @@ -105,747 +130,722 @@ class NetworkService { async getMonitorsAndSummaryByTeamId(config) { const params = new URLSearchParams(); - if (config.types) { - config.types.forEach((type) => { - params.append("type", type); - }); - } - return this.axiosInstance.get( - `/monitors/team/summary/${config.teamId}?${params.toString()}`, - { - headers: { - Authorization: `Bearer ${config.authToken}`, - "Content-Type": "application/json", - }, - } - ); - } + if (config.types) { + config.types.forEach((type) => { + params.append("type", type); + }); + } + return this.axiosInstance.get( + `/monitors/team/summary/${config.teamId}?${params.toString()}`, + { + headers: { + Authorization: `Bearer ${config.authToken}`, + "Content-Type": "application/json", + }, + } + ); + } - /** - * ************************************ - * Get all uptime monitors for a Team - * ************************************ - * - * @async - * @param {Object} config - The configuration object. - * @param {string} config.authToken - The authorization token to be used in the request header. - * @param {string} config.teamId - The ID of the team whose monitors are to be retrieved. - * @param {number} [config.limit] - The maximum number of checks to retrieve. 0 for all, -1 for none - * @param {Array} [config.types] - The types of monitors to retrieve. - * @param {string} [config.status] - The status of the monitors to retrieve. - * @param {string} [config.checkOrder] - The order in which to sort the retrieved monitors. - * @param {boolean} [config.normalize] - Whether to normalize the retrieved monitors. - * @param {number} [config.page] - The page number for pagination. - * @param {number} [config.rowsPerPage] - The number of rows per page for pagination. - * @param {string} [config.filter] - The filter to apply to the monitors. - * @param {string} [config.field] - The field to sort by. - * @param {string} [config.order] - The order in which to sort the field. - * @returns {Promise} The response from the axios GET request. - */ - async getMonitorsByTeamId(config) { - const { - authToken, - teamId, - limit, - types, - status, - checkOrder, - normalize, - page, - rowsPerPage, - filter, - field, - order, - } = config; + /** + * ************************************ + * Get all uptime monitors for a Team + * ************************************ + * + * @async + * @param {Object} config - The configuration object. + * @param {string} config.authToken - The authorization token to be used in the request header. + * @param {string} config.teamId - The ID of the team whose monitors are to be retrieved. + * @param {number} [config.limit] - The maximum number of checks to retrieve. 0 for all, -1 for none + * @param {Array} [config.types] - The types of monitors to retrieve. + * @param {string} [config.status] - The status of the monitors to retrieve. + * @param {string} [config.checkOrder] - The order in which to sort the retrieved monitors. + * @param {boolean} [config.normalize] - Whether to normalize the retrieved monitors. + * @param {number} [config.page] - The page number for pagination. + * @param {number} [config.rowsPerPage] - The number of rows per page for pagination. + * @param {string} [config.filter] - The filter to apply to the monitors. + * @param {string} [config.field] - The field to sort by. + * @param {string} [config.order] - The order in which to sort the field. + * @returns {Promise} The response from the axios GET request. + */ + async getMonitorsByTeamId(config) { + const { + authToken, + teamId, + limit, + types, + status, + checkOrder, + normalize, + page, + rowsPerPage, + filter, + field, + order, + } = config; - const params = new URLSearchParams(); + const params = new URLSearchParams(); - if (limit) params.append("limit", limit); - if (types) { - types.forEach((type) => { - params.append("type", type); - }); - } - if (status) params.append("status", status); - if (checkOrder) params.append("checkOrder", checkOrder); - if (normalize) params.append("normalize", normalize); - if (page) params.append("page", page); - if (rowsPerPage) params.append("rowsPerPage", rowsPerPage); - if (filter) params.append("filter", filter); - if (field) params.append("field", field); - if (order) params.append("order", order); + if (limit) params.append("limit", limit); + if (types) { + types.forEach((type) => { + params.append("type", type); + }); + } + if (status) params.append("status", status); + if (checkOrder) params.append("checkOrder", checkOrder); + if (normalize) params.append("normalize", normalize); + if (page) params.append("page", page); + if (rowsPerPage) params.append("rowsPerPage", rowsPerPage); + if (filter) params.append("filter", filter); + if (field) params.append("field", field); + if (order) params.append("order", order); - return this.axiosInstance.get( - `/monitors/team/${teamId}?${params.toString()}`, - { - headers: { - Authorization: `Bearer ${authToken}`, - "Content-Type": "application/json", - }, - } - ); - } + return this.axiosInstance.get(`/monitors/team/${teamId}?${params.toString()}`, { + headers: { + Authorization: `Bearer ${authToken}`, + "Content-Type": "application/json", + }, + }); + } - /** - * ************************************ - * Get stats for a monitor - * ************************************ - * - * @async - * @param {Object} config - The configuration object. - * @param {string} config.authToken - The authorization token to be used in the request header. - * @param {string} config.monitorId - The ID of the monitor whose statistics are to be retrieved. - * @param {string} config.sortOrder - The order in which to sort the retrieved statistics. - * @param {number} config.limit - The maximum number of statistics to retrieve. - * @param {string} config.dateRange - The date range for which to retrieve statistics. - * @param {number} config.numToDisplay - The number of checks to display. - * @param {boolean} config.normalize - Whether to normalize the retrieved statistics. - * @returns {Promise} The response from the axios GET request. - */ - async getStatsByMonitorId(config) { - const params = new URLSearchParams(); - if (config.sortOrder) params.append("sortOrder", config.sortOrder); - if (config.limit) params.append("limit", config.limit); - if (config.dateRange) params.append("dateRange", config.dateRange); - if (config.numToDisplay) params.append("numToDisplay", config.numToDisplay); - if (config.normalize) params.append("normalize", config.normalize); + /** + * ************************************ + * Get stats for a monitor + * ************************************ + * + * @async + * @param {Object} config - The configuration object. + * @param {string} config.authToken - The authorization token to be used in the request header. + * @param {string} config.monitorId - The ID of the monitor whose statistics are to be retrieved. + * @param {string} config.sortOrder - The order in which to sort the retrieved statistics. + * @param {number} config.limit - The maximum number of statistics to retrieve. + * @param {string} config.dateRange - The date range for which to retrieve statistics. + * @param {number} config.numToDisplay - The number of checks to display. + * @param {boolean} config.normalize - Whether to normalize the retrieved statistics. + * @returns {Promise} The response from the axios GET request. + */ + async getStatsByMonitorId(config) { + const params = new URLSearchParams(); + if (config.sortOrder) params.append("sortOrder", config.sortOrder); + if (config.limit) params.append("limit", config.limit); + if (config.dateRange) params.append("dateRange", config.dateRange); + if (config.numToDisplay) params.append("numToDisplay", config.numToDisplay); + if (config.normalize) params.append("normalize", config.normalize); - return this.axiosInstance.get( - `/monitors/stats/${config.monitorId}?${params.toString()}`, - { - headers: { - Authorization: `Bearer ${config.authToken}`, - }, - } - ); - } + return this.axiosInstance.get( + `/monitors/stats/${config.monitorId}?${params.toString()}`, + { + headers: { + Authorization: `Bearer ${config.authToken}`, + }, + } + ); + } - /** - * ************************************ - * Updates a single monitor - * ************************************ - * - * @async - * @param {Object} config - The configuration object. - * @param {string} config.authToken - The authorization token to be used in the request header. - * @param {string} config.monitorId - The ID of the monitor to be updated. - * @param {Object} config.updatedFields - The fields to be updated for the monitor. - * @returns {Promise} The response from the axios PUT request. - */ - async updateMonitor(config) { - return this.axiosInstance.put( - `/monitors/${config.monitorId}`, - config.updatedFields, - { - headers: { - Authorization: `Bearer ${config.authToken}`, - "Content-Type": "application/json", - }, - } - ); - } + /** + * ************************************ + * Updates a single monitor + * ************************************ + * + * @async + * @param {Object} config - The configuration object. + * @param {string} config.authToken - The authorization token to be used in the request header. + * @param {string} config.monitorId - The ID of the monitor to be updated. + * @param {Object} config.updatedFields - The fields to be updated for the monitor. + * @returns {Promise} The response from the axios PUT request. + */ + async updateMonitor(config) { + return this.axiosInstance.put(`/monitors/${config.monitorId}`, config.updatedFields, { + headers: { + Authorization: `Bearer ${config.authToken}`, + "Content-Type": "application/json", + }, + }); + } - /** - * ************************************ - * Deletes a single monitor by its ID - * ************************************ - * - * @async - * @param {Object} config - The configuration object. - * @param {string} config.authToken - The authorization token to be used in the request header. - * @param {string} config.monitorId - The ID of the monitor to be deleted. - * @returns {Promise} The response from the axios DELETE request. - */ - async deleteMonitorById(config) { - return this.axiosInstance.delete(`/monitors/${config.monitorId}`, { - headers: { - Authorization: `Bearer ${config.authToken}`, - "Content-Type": "application/json", - }, - }); - } + /** + * ************************************ + * Deletes a single monitor by its ID + * ************************************ + * + * @async + * @param {Object} config - The configuration object. + * @param {string} config.authToken - The authorization token to be used in the request header. + * @param {string} config.monitorId - The ID of the monitor to be deleted. + * @returns {Promise} The response from the axios DELETE request. + */ + async deleteMonitorById(config) { + return this.axiosInstance.delete(`/monitors/${config.monitorId}`, { + headers: { + Authorization: `Bearer ${config.authToken}`, + "Content-Type": "application/json", + }, + }); + } - /** - * ************************************ - * Deletes all checks for all monitor by teamID - * ************************************ - * - * @async - * @param {Object} config - The configuration object. - * @param {string} config.authToken - The authorization token to be used in the request header. - * @param {string} config.teamId - The team ID of the monitors to be deleted. - * @returns {Promise} The response from the axios DELETE request. - */ - async deleteChecksByTeamId(config) { - return this.axiosInstance.delete(`/checks/team/${config.teamId}`, { - headers: { - Authorization: `Bearer ${config.authToken}`, - "Content-Type": "application/json", - }, - }); - } - /** - * ************************************ - * Pauses a single monitor by its ID - * ************************************ - * - * @async - * @param {Object} config - The configuration object. - * @param {string} config.authToken - The authorization token to be used in the request header. - * @param {string} config.monitorId - The ID of the monitor to be paused. - * @returns {Promise} The response from the axios POST request. - */ - async pauseMonitorById(config) { - return this.axiosInstance.post( - `/monitors/pause/${config.monitorId}`, - {}, - { - headers: { - Authorization: `Bearer ${config.authToken}`, - "Content-Type": "application/json", - }, - } - ); - } + /** + * ************************************ + * Deletes all checks for all monitor by teamID + * ************************************ + * + * @async + * @param {Object} config - The configuration object. + * @param {string} config.authToken - The authorization token to be used in the request header. + * @param {string} config.teamId - The team ID of the monitors to be deleted. + * @returns {Promise} The response from the axios DELETE request. + */ + async deleteChecksByTeamId(config) { + return this.axiosInstance.delete(`/checks/team/${config.teamId}`, { + headers: { + Authorization: `Bearer ${config.authToken}`, + "Content-Type": "application/json", + }, + }); + } + /** + * ************************************ + * Pauses a single monitor by its ID + * ************************************ + * + * @async + * @param {Object} config - The configuration object. + * @param {string} config.authToken - The authorization token to be used in the request header. + * @param {string} config.monitorId - The ID of the monitor to be paused. + * @returns {Promise} The response from the axios POST request. + */ + async pauseMonitorById(config) { + return this.axiosInstance.post( + `/monitors/pause/${config.monitorId}`, + {}, + { + headers: { + Authorization: `Bearer ${config.authToken}`, + "Content-Type": "application/json", + }, + } + ); + } - /** - * ************************************ - * Adds demo monitors - * ************************************ - * - * @async - * @param {Object} config - The configuration object. - * @param {string} config.authToken - The authorization token to be used in the request header. - * @returns {Promise} The response from the axios POST request. - */ - async addDemoMonitors(config) { - return this.axiosInstance.post( - `/monitors/demo`, - {}, - { - headers: { - Authorization: `Bearer ${config.authToken}`, - "Content-Type": "application/json", - }, - } - ); - } + /** + * ************************************ + * Adds demo monitors + * ************************************ + * + * @async + * @param {Object} config - The configuration object. + * @param {string} config.authToken - The authorization token to be used in the request header. + * @returns {Promise} The response from the axios POST request. + */ + async addDemoMonitors(config) { + return this.axiosInstance.post( + `/monitors/demo`, + {}, + { + headers: { + Authorization: `Bearer ${config.authToken}`, + "Content-Type": "application/json", + }, + } + ); + } - /** - * ************************************ - * Deletes all monitors for a team by team ID - * ************************************ - * - * @async - * @param {Object} config - The configuration object. - * @param {string} config.authToken - The authorization token to be used in the request header. - * @returns {Promise} The response from the axios DELETE request. - */ - async deleteAllMonitors(config) { - return this.axiosInstance.delete(`/monitors/`, { - headers: { - Authorization: `Bearer ${config.authToken}`, - "Content-Type": "application/json", - }, - }); - } + /** + * ************************************ + * Deletes all monitors for a team by team ID + * ************************************ + * + * @async + * @param {Object} config - The configuration object. + * @param {string} config.authToken - The authorization token to be used in the request header. + * @returns {Promise} The response from the axios DELETE request. + */ + async deleteAllMonitors(config) { + return this.axiosInstance.delete(`/monitors/`, { + headers: { + Authorization: `Bearer ${config.authToken}`, + "Content-Type": "application/json", + }, + }); + } - /** - * ************************************ - * Gets the certificate expiry for a monitor - * ************************************ - * - * @async - * @param {Object} config - The configuration object. - * @param {string} config.authToken - The authorization token to be used in the request header. - * @param {string} config.monitorId - The ID of the monitor whose certificate expiry is to be retrieved. - * @returns {Promise} The response from the axios GET request. - * - */ - async getCertificateExpiry(config) { - return this.axiosInstance.get(`/monitors/certificate/${config.monitorId}`, { - headers: { - Authorization: `Bearer ${config.authToken}`, - }, - }); - } + /** + * ************************************ + * Gets the certificate expiry for a monitor + * ************************************ + * + * @async + * @param {Object} config - The configuration object. + * @param {string} config.authToken - The authorization token to be used in the request header. + * @param {string} config.monitorId - The ID of the monitor whose certificate expiry is to be retrieved. + * @returns {Promise} The response from the axios GET request. + * + */ + async getCertificateExpiry(config) { + return this.axiosInstance.get(`/monitors/certificate/${config.monitorId}`, { + headers: { + Authorization: `Bearer ${config.authToken}`, + }, + }); + } - /** - * ************************************ - * Registers a new user - * ************************************ - * - * @async - * @param {Object} form - The form data for the new user to be registered. - * @returns {Promise} The response from the axios POST request. - */ - async registerUser(form) { - return this.axiosInstance.post(`/auth/register`, form); - } + /** + * ************************************ + * Registers a new user + * ************************************ + * + * @async + * @param {Object} form - The form data for the new user to be registered. + * @returns {Promise} The response from the axios POST request. + */ + async registerUser(form) { + return this.axiosInstance.post(`/auth/register`, form); + } - /** - * ************************************ - * Logs in a user - * ************************************ - * - * @async - * @param {Object} form - The form data for the user to be logged in. - * @returns {Promise} The response from the axios POST request. - * - */ - async loginUser(form) { - return this.axiosInstance.post(`/auth/login`, form); - } + /** + * ************************************ + * Logs in a user + * ************************************ + * + * @async + * @param {Object} form - The form data for the user to be logged in. + * @returns {Promise} The response from the axios POST request. + * + */ + async loginUser(form) { + return this.axiosInstance.post(`/auth/login`, form); + } - /** - * ************************************ - * Updates a user - * ************************************ - * - * @async - * @param {Object} config - The configuration object. - * @param {string} config.authToken - The authorization token to be used in the request header. - * @param {string} config.userId - The ID of the user to be updated. - * @param {Object} config.form - The form data for the user to be updated. - * @returns {Promise} The response from the axios PUT request. - * - */ - async updateUser(config) { - return this.axiosInstance.put(`/auth/user/${config.userId}`, config.form, { - headers: { - Authorization: `Bearer ${config.authToken}`, - }, - }); - } + /** + * ************************************ + * Updates a user + * ************************************ + * + * @async + * @param {Object} config - The configuration object. + * @param {string} config.authToken - The authorization token to be used in the request header. + * @param {string} config.userId - The ID of the user to be updated. + * @param {Object} config.form - The form data for the user to be updated. + * @returns {Promise} The response from the axios PUT request. + * + */ + async updateUser(config) { + return this.axiosInstance.put(`/auth/user/${config.userId}`, config.form, { + headers: { + Authorization: `Bearer ${config.authToken}`, + }, + }); + } - /** - * ************************************ - * Deletes a user - * ************************************ - * - * @async - * @param {Object} config - The configuration object. - * @param {string} config.authToken - The authorization token to be used in the request header. - * @param {string} config.userId - The ID of the user to be deleted. - * - **/ - async deleteUser(config) { - return this.axiosInstance.delete(`/auth/user/${config.userId}`, { - headers: { Authorization: `Bearer ${config.authToken}` }, - }); - } + /** + * ************************************ + * Deletes a user + * ************************************ + * + * @async + * @param {Object} config - The configuration object. + * @param {string} config.authToken - The authorization token to be used in the request header. + * @param {string} config.userId - The ID of the user to be deleted. + * + **/ + async deleteUser(config) { + return this.axiosInstance.delete(`/auth/user/${config.userId}`, { + headers: { Authorization: `Bearer ${config.authToken}` }, + }); + } - /** - * ************************************ - * Forgot password request - * ************************************ - * - * @async - * @param {Object} form - The form data for the password recovery request. - * @returns {Promise} The response from the axios POST request. - * - */ - async forgotPassword(form) { - return this.axiosInstance.post(`/auth/recovery/request`, form); - } + /** + * ************************************ + * Forgot password request + * ************************************ + * + * @async + * @param {Object} form - The form data for the password recovery request. + * @returns {Promise} The response from the axios POST request. + * + */ + async forgotPassword(form) { + return this.axiosInstance.post(`/auth/recovery/request`, form); + } - /** - * ************************************ - * Validates a recovery token - * ************************************ - * - * @async - * @param {Object} config - The configuration object. - * @param {string} config.recoveryToken - The recovery token to be validated. - * @returns {Promise} The response from the axios POST request. - * - */ - async validateRecoveryToken(config) { - return this.axiosInstance.post("/auth/recovery/validate", { - recoveryToken: config.recoveryToken, - }); - } + /** + * ************************************ + * Validates a recovery token + * ************************************ + * + * @async + * @param {Object} config - The configuration object. + * @param {string} config.recoveryToken - The recovery token to be validated. + * @returns {Promise} The response from the axios POST request. + * + */ + async validateRecoveryToken(config) { + return this.axiosInstance.post("/auth/recovery/validate", { + recoveryToken: config.recoveryToken, + }); + } - /** - * ************************************ - * Requests password recovery - * ************************************ - * - * @async - * @param {Object} config - The configuration object. - * @param {string} config.recoveryToken - The token for recovery request. - * @param {Object} config.form - The form data for the password recovery request. - * @returns {Promise} The response from the axios POST request. - * - */ - async setNewPassword(config) { - return this.axiosInstance.post("/auth/recovery/reset", { - ...config.form, - recoveryToken: config.recoveryToken, - }); - } + /** + * ************************************ + * Requests password recovery + * ************************************ + * + * @async + * @param {Object} config - The configuration object. + * @param {string} config.recoveryToken - The token for recovery request. + * @param {Object} config.form - The form data for the password recovery request. + * @returns {Promise} The response from the axios POST request. + * + */ + async setNewPassword(config) { + return this.axiosInstance.post("/auth/recovery/reset", { + ...config.form, + recoveryToken: config.recoveryToken, + }); + } - /** - * ************************************ - * Checks if an admin user exists - * ************************************ - * - * @async - * @returns {Promise} The response from the axios GET request. - * - */ - async doesSuperAdminExist() { - return this.axiosInstance.get("/auth/users/superadmin"); - } + /** + * ************************************ + * Checks if an admin user exists + * ************************************ + * + * @async + * @returns {Promise} The response from the axios GET request. + * + */ + async doesSuperAdminExist() { + return this.axiosInstance.get("/auth/users/superadmin"); + } - /** - * ************************************ - * Get all users - * ************************************ - * - * @async - * @param {Object} config - The configuration object. - * @param {string} config.authToken - The authorization token to be used in the request header. - * @returns {Promise} The response from the axios GET request. - * - */ - async getAllUsers(config) { - return this.axiosInstance.get("/auth/users", { - headers: { Authorization: `Bearer ${config.authToken}` }, - }); - } + /** + * ************************************ + * Get all users + * ************************************ + * + * @async + * @param {Object} config - The configuration object. + * @param {string} config.authToken - The authorization token to be used in the request header. + * @returns {Promise} The response from the axios GET request. + * + */ + async getAllUsers(config) { + return this.axiosInstance.get("/auth/users", { + headers: { Authorization: `Bearer ${config.authToken}` }, + }); + } - /** - * ************************************ - * Requests an invitation token - * ************************************ - * - * @async - * @param {Object} config - The configuration object. - * @param {string} config.authToken - The authorization token to be used in the request header. - * @param {string} config.email - The email of the user to be invited. - * @param {string} config.role - The role of the user to be invited. - * @returns {Promise} The response from the axios POST request. - * - */ - async requestInvitationToken(config) { - return this.axiosInstance.post( - `/invite`, - { email: config.email, role: config.role }, - { - headers: { Authorization: `Bearer ${config.authToken}` }, - } - ); - } + /** + * ************************************ + * Requests an invitation token + * ************************************ + * + * @async + * @param {Object} config - The configuration object. + * @param {string} config.authToken - The authorization token to be used in the request header. + * @param {string} config.email - The email of the user to be invited. + * @param {string} config.role - The role of the user to be invited. + * @returns {Promise} The response from the axios POST request. + * + */ + async requestInvitationToken(config) { + return this.axiosInstance.post( + `/invite`, + { email: config.email, role: config.role }, + { + headers: { Authorization: `Bearer ${config.authToken}` }, + } + ); + } - /** - * ************************************ - * Verifies an invitation token - * ************************************ - * - * @async - * @param {string} token - The invitation token to be verified. - * @returns {Promise} The response from the axios POST request. - * - */ - async verifyInvitationToken(token) { - return this.axiosInstance.post(`/invite/verify`, { - token, - }); - } + /** + * ************************************ + * Verifies an invitation token + * ************************************ + * + * @async + * @param {string} token - The invitation token to be verified. + * @returns {Promise} The response from the axios POST request. + * + */ + async verifyInvitationToken(token) { + return this.axiosInstance.post(`/invite/verify`, { + token, + }); + } - /** - * ************************************ - * Get all checks for a given monitor - * ************************************ - * - * @async - * @param {Object} config - The configuration object. - * @param {string} config.authToken - The authorization token to be used in the request header. - * @param {string} config.monitorId - The ID of the monitor. - * @param {string} config.sortOrder - The order in which to sort the checks. - * @param {number} config.limit - The maximum number of checks to retrieve. - * @param {string} config.dateRange - The range of dates for which to retrieve checks. - * @param {string} config.filter - The filter to apply to the checks. - * @param {number} config.page - The page number to retrieve in a paginated list. - * @param {number} config.rowsPerPage - The number of rows per page in a paginated list. - * @returns {Promise} The response from the axios GET request. - * - */ + /** + * ************************************ + * Get all checks for a given monitor + * ************************************ + * + * @async + * @param {Object} config - The configuration object. + * @param {string} config.authToken - The authorization token to be used in the request header. + * @param {string} config.monitorId - The ID of the monitor. + * @param {string} config.sortOrder - The order in which to sort the checks. + * @param {number} config.limit - The maximum number of checks to retrieve. + * @param {string} config.dateRange - The range of dates for which to retrieve checks. + * @param {string} config.filter - The filter to apply to the checks. + * @param {number} config.page - The page number to retrieve in a paginated list. + * @param {number} config.rowsPerPage - The number of rows per page in a paginated list. + * @returns {Promise} The response from the axios GET request. + * + */ - async getChecksByMonitor(config) { - const params = new URLSearchParams(); - if (config.sortOrder) params.append("sortOrder", config.sortOrder); - if (config.limit) params.append("limit", config.limit); - if (config.dateRange) params.append("dateRange", config.dateRange); - if (config.filter) params.append("filter", config.filter); - if (config.page) params.append("page", config.page); - if (config.rowsPerPage) params.append("rowsPerPage", config.rowsPerPage); + async getChecksByMonitor(config) { + const params = new URLSearchParams(); + if (config.sortOrder) params.append("sortOrder", config.sortOrder); + if (config.limit) params.append("limit", config.limit); + if (config.dateRange) params.append("dateRange", config.dateRange); + if (config.filter) params.append("filter", config.filter); + if (config.page) params.append("page", config.page); + if (config.rowsPerPage) params.append("rowsPerPage", config.rowsPerPage); - return this.axiosInstance.get( - `/checks/${config.monitorId}?${params.toString()}`, - { - headers: { Authorization: `Bearer ${config.authToken}` }, - } - ); - } + return this.axiosInstance.get(`/checks/${config.monitorId}?${params.toString()}`, { + headers: { Authorization: `Bearer ${config.authToken}` }, + }); + } - /** - * ************************************ - * Get all checks for a given user - * ************************************ - * - * @async - * @param {Object} config - The configuration object. - * @param {string} config.authToken - The authorization token to be used in the request header. - * @param {string} config.userId - The ID of the user. - * @param {string} config.sortOrder - The order in which to sort the checks. - * @param {number} config.limit - The maximum number of checks to retrieve. - * @param {string} config.dateRange - The range of dates for which to retrieve checks. - * @param {string} config.filter - The filter to apply to the checks. - * @param {number} config.page - The page number to retrieve in a paginated list. - * @param {number} config.rowsPerPage - The number of rows per page in a paginated list. - * @returns {Promise} The response from the axios GET request. - * - */ - async getChecksByTeam(config) { - const params = new URLSearchParams(); - if (config.sortOrder) params.append("sortOrder", config.sortOrder); - if (config.limit) params.append("limit", config.limit); - if (config.dateRange) params.append("dateRange", config.dateRange); - if (config.filter) params.append("filter", config.filter); - if (config.page) params.append("page", config.page); - if (config.rowsPerPage) params.append("rowsPerPage", config.rowsPerPage); - return this.axiosInstance.get( - `/checks/team/${config.teamId}?${params.toString()}`, - { - headers: { Authorization: `Bearer ${config.authToken}` }, - } - ); - } + /** + * ************************************ + * Get all checks for a given user + * ************************************ + * + * @async + * @param {Object} config - The configuration object. + * @param {string} config.authToken - The authorization token to be used in the request header. + * @param {string} config.userId - The ID of the user. + * @param {string} config.sortOrder - The order in which to sort the checks. + * @param {number} config.limit - The maximum number of checks to retrieve. + * @param {string} config.dateRange - The range of dates for which to retrieve checks. + * @param {string} config.filter - The filter to apply to the checks. + * @param {number} config.page - The page number to retrieve in a paginated list. + * @param {number} config.rowsPerPage - The number of rows per page in a paginated list. + * @returns {Promise} The response from the axios GET request. + * + */ + async getChecksByTeam(config) { + const params = new URLSearchParams(); + if (config.sortOrder) params.append("sortOrder", config.sortOrder); + if (config.limit) params.append("limit", config.limit); + if (config.dateRange) params.append("dateRange", config.dateRange); + if (config.filter) params.append("filter", config.filter); + if (config.page) params.append("page", config.page); + if (config.rowsPerPage) params.append("rowsPerPage", config.rowsPerPage); + return this.axiosInstance.get(`/checks/team/${config.teamId}?${params.toString()}`, { + headers: { Authorization: `Bearer ${config.authToken}` }, + }); + } - /** - * ************************************ - * Get all checks for a given user - * ************************************ - * - * @async - * @param {Object} config - The configuration object. - * @param {string} config.authToken - The authorization token to be used in the request header. - * @param {number} config.ttl - TTL for checks - * @returns {Promise} The response from the axios GET request. - * - */ - async updateChecksTTL(config) { - return this.axiosInstance.put( - `/checks/team/ttl`, - { ttl: config.ttl }, - { - headers: { - Authorization: `Bearer ${config.authToken}`, - "Content-Type": "application/json", - }, - } - ); - } + /** + * ************************************ + * Get all checks for a given user + * ************************************ + * + * @async + * @param {Object} config - The configuration object. + * @param {string} config.authToken - The authorization token to be used in the request header. + * @param {number} config.ttl - TTL for checks + * @returns {Promise} The response from the axios GET request. + * + */ + async updateChecksTTL(config) { + return this.axiosInstance.put( + `/checks/team/ttl`, + { ttl: config.ttl }, + { + headers: { + Authorization: `Bearer ${config.authToken}`, + "Content-Type": "application/json", + }, + } + ); + } - /** - * ************************************ - * Get app settings - * ************************************ - * - * @async - * @param {Object} config - The configuration object. - * @param {string} config.authToken - The authorization token to be used in the request header. - * @returns {Promise} The response from the axios GET request. - * - */ + /** + * ************************************ + * Get app settings + * ************************************ + * + * @async + * @param {Object} config - The configuration object. + * @param {string} config.authToken - The authorization token to be used in the request header. + * @returns {Promise} The response from the axios GET request. + * + */ - async getAppSettings(config) { - return this.axiosInstance.get("/settings", { - headers: { - Authorization: `Bearer ${config.authToken}`, - "Content-Type": "application/json", - }, - }); - } + async getAppSettings(config) { + return this.axiosInstance.get("/settings", { + headers: { + Authorization: `Bearer ${config.authToken}`, + "Content-Type": "application/json", + }, + }); + } - /** - * - * ************************************ - * Create a new monitor - * ************************************ - * - * @async - * @param {Object} config - The configuration object. - * @param {string} config.authToken - The authorization token to be used in the request header. - * @param {Object} config.settings - The monitor object to be sent in the request body. - * @returns {Promise} The response from the axios POST request. - */ - async updateAppSettings(config) { - return this.axiosInstance.put(`/settings`, config.settings, { - headers: { - Authorization: `Bearer ${config.authToken}`, - "Content-Type": "application/json", - }, - }); - } + /** + * + * ************************************ + * Create a new monitor + * ************************************ + * + * @async + * @param {Object} config - The configuration object. + * @param {string} config.authToken - The authorization token to be used in the request header. + * @param {Object} config.settings - The monitor object to be sent in the request body. + * @returns {Promise} The response from the axios POST request. + */ + async updateAppSettings(config) { + return this.axiosInstance.put(`/settings`, config.settings, { + headers: { + Authorization: `Bearer ${config.authToken}`, + "Content-Type": "application/json", + }, + }); + } - /** - * ************************************ - * Creates a maintenance window - * ************************************ - * - * @async - * @param {Object} config - The configuration object. - * @param {string} config.authToken - The authorization token to be used in the request header. - * @param {Object} config.maintenanceWindow - The maintenance window object to be sent in the request body. - * @returns {Promise} The response from the axios POST request. - * - */ + /** + * ************************************ + * Creates a maintenance window + * ************************************ + * + * @async + * @param {Object} config - The configuration object. + * @param {string} config.authToken - The authorization token to be used in the request header. + * @param {Object} config.maintenanceWindow - The maintenance window object to be sent in the request body. + * @returns {Promise} The response from the axios POST request. + * + */ - async createMaintenanceWindow(config) { - return this.axiosInstance.post( - `/maintenance-window`, - config.maintenanceWindow, - { - headers: { - Authorization: `Bearer ${config.authToken}`, - "Content-Type": "application/json", - }, - } - ); - } + async createMaintenanceWindow(config) { + return this.axiosInstance.post(`/maintenance-window`, config.maintenanceWindow, { + headers: { + Authorization: `Bearer ${config.authToken}`, + "Content-Type": "application/json", + }, + }); + } - /** - * ************************************ - * Edits a maintenance window - * ************************************ - * - * @async - * @param {Object} config - The configuration object. - * @param {string} config.authToken - The authorization token to be used in the request header. - * @param {Object} config.maintenanceWindowId - The maintenance window id. - * @param {Object} config.maintenanceWindow - The maintenance window object to be sent in the request body. - * @returns {Promise} The response from the axios POST request. - * - */ + /** + * ************************************ + * Edits a maintenance window + * ************************************ + * + * @async + * @param {Object} config - The configuration object. + * @param {string} config.authToken - The authorization token to be used in the request header. + * @param {Object} config.maintenanceWindowId - The maintenance window id. + * @param {Object} config.maintenanceWindow - The maintenance window object to be sent in the request body. + * @returns {Promise} The response from the axios POST request. + * + */ - async editMaintenanceWindow(config) { - return this.axiosInstance.put( - `/maintenance-window/${config.maintenanceWindowId}`, - config.maintenanceWindow, - { - headers: { - Authorization: `Bearer ${config.authToken}`, - "Content-Type": "application/json", - }, - } - ); - } + async editMaintenanceWindow(config) { + return this.axiosInstance.put( + `/maintenance-window/${config.maintenanceWindowId}`, + config.maintenanceWindow, + { + headers: { + Authorization: `Bearer ${config.authToken}`, + "Content-Type": "application/json", + }, + } + ); + } - /** - * ************************************ - * Get maintenance window by id - * ************************************ - * - * @async - * @param {Object} config - The configuration object. - * @param {string} config.authToken - The authorization token to be used in the request header. - * @param {string} [config.maintenanceWindowId] - The id of the maintenance window to delete. - * @returns {Promise} The response from the axios POST request. - * - */ + /** + * ************************************ + * Get maintenance window by id + * ************************************ + * + * @async + * @param {Object} config - The configuration object. + * @param {string} config.authToken - The authorization token to be used in the request header. + * @param {string} [config.maintenanceWindowId] - The id of the maintenance window to delete. + * @returns {Promise} The response from the axios POST request. + * + */ - async getMaintenanceWindowById(config) { - const { authToken, maintenanceWindowId } = config; - return this.axiosInstance.get( - `/maintenance-window/${maintenanceWindowId}`, - { - headers: { - Authorization: `Bearer ${authToken}`, - "Content-Type": "application/json", - }, - } - ); - } + async getMaintenanceWindowById(config) { + const { authToken, maintenanceWindowId } = config; + return this.axiosInstance.get(`/maintenance-window/${maintenanceWindowId}`, { + headers: { + Authorization: `Bearer ${authToken}`, + "Content-Type": "application/json", + }, + }); + } - /** - * ************************************ - * Get maintenance windows by teamId - * ************************************ - * - * @async - * @param {Object} config - The configuration object. - * @param {string} config.authToken - The authorization token to be used in the request header. - * @param {string} [config.active] - The status of the maintenance windows to retrieve. - * @param {number} [config.page] - The page number for pagination. - * @param {number} [config.rowsPerPage] - The number of rows per page for pagination. - * @param {string} [config.field] - The field to sort by. - * @param {string} [config.order] - The order in which to sort the field. - * @returns {Promise} The response from the axios POST request. - * - */ + /** + * ************************************ + * Get maintenance windows by teamId + * ************************************ + * + * @async + * @param {Object} config - The configuration object. + * @param {string} config.authToken - The authorization token to be used in the request header. + * @param {string} [config.active] - The status of the maintenance windows to retrieve. + * @param {number} [config.page] - The page number for pagination. + * @param {number} [config.rowsPerPage] - The number of rows per page for pagination. + * @param {string} [config.field] - The field to sort by. + * @param {string} [config.order] - The order in which to sort the field. + * @returns {Promise} The response from the axios POST request. + * + */ - async getMaintenanceWindowsByTeamId(config) { - const { authToken, active, page, rowsPerPage, field, order } = config; - const params = new URLSearchParams(); + async getMaintenanceWindowsByTeamId(config) { + const { authToken, active, page, rowsPerPage, field, order } = config; + const params = new URLSearchParams(); - if (active) params.append("status", active); - if (page) params.append("page", page); - if (rowsPerPage) params.append("rowsPerPage", rowsPerPage); - if (field) params.append("field", field); - if (order) params.append("order", order); + if (active) params.append("status", active); + if (page) params.append("page", page); + if (rowsPerPage) params.append("rowsPerPage", rowsPerPage); + if (field) params.append("field", field); + if (order) params.append("order", order); - return this.axiosInstance.get( - `/maintenance-window/team?${params.toString()}`, - { - headers: { - Authorization: `Bearer ${authToken}`, - "Content-Type": "application/json", - }, - } - ); - } - /** - * ************************************ - * Delete maintenance window by id - * ************************************ - * - * @async - * @param {Object} config - The configuration object. - * @param {string} config.authToken - The authorization token to be used in the request header. - * @param {string} [config.maintenanceWindowId] - The id of the maintenance window to delete. - * @returns {Promise} The response from the axios POST request. - * - */ + return this.axiosInstance.get(`/maintenance-window/team?${params.toString()}`, { + headers: { + Authorization: `Bearer ${authToken}`, + "Content-Type": "application/json", + }, + }); + } + /** + * ************************************ + * Delete maintenance window by id + * ************************************ + * + * @async + * @param {Object} config - The configuration object. + * @param {string} config.authToken - The authorization token to be used in the request header. + * @param {string} [config.maintenanceWindowId] - The id of the maintenance window to delete. + * @returns {Promise} The response from the axios POST request. + * + */ - async deleteMaintenanceWindow(config) { - const { authToken, maintenanceWindowId } = config; - return this.axiosInstance.delete( - `/maintenance-window/${maintenanceWindowId}`, - { - headers: { - Authorization: `Bearer ${authToken}`, - "Content-Type": "application/json", - }, - } - ); - } - - /** - * ************************************ - * Fetcher github latest release version - * ************************************ - * - * @async - * @returns {Promise} The response from the axios GET request. - * - */ - async fetchGithubLatestRelease() { - return this.axiosInstance.get('https://api.github.com/repos/bluewave-labs/bluewave-uptime/releases/latest'); - } + async deleteMaintenanceWindow(config) { + const { authToken, maintenanceWindowId } = config; + return this.axiosInstance.delete(`/maintenance-window/${maintenanceWindowId}`, { + headers: { + Authorization: `Bearer ${authToken}`, + "Content-Type": "application/json", + }, + }); + } + /** + * ************************************ + * Fetcher github latest release version + * ************************************ + * + * @async + * @returns {Promise} The response from the axios GET request. + * + */ + async fetchGithubLatestRelease() { + return this.axiosInstance.get( + "https://api.github.com/repos/bluewave-labs/bluewave-uptime/releases/latest" + ); + } } export default NetworkService; @@ -853,6 +853,6 @@ export default NetworkService; let networkService; export const setNetworkService = (service) => { - networkService = service; + networkService = service; }; export { networkService }; diff --git a/Client/src/Utils/NetworkServiceProvider.jsx b/Client/src/Utils/NetworkServiceProvider.jsx index 4c66681e0..9fb65f1ca 100644 --- a/Client/src/Utils/NetworkServiceProvider.jsx +++ b/Client/src/Utils/NetworkServiceProvider.jsx @@ -5,11 +5,11 @@ import NetworkService from "./NetworkService"; import { store } from "../store"; const NetworkServiceProvider = ({ children }) => { - const dispatch = useDispatch(); - const navigate = useNavigate(); - const networkService = new NetworkService(store, dispatch, navigate); - setNetworkService(networkService); - return children; + const dispatch = useDispatch(); + const navigate = useNavigate(); + const networkService = new NetworkService(store, dispatch, navigate); + setNetworkService(networkService); + return children; }; export default NetworkServiceProvider; diff --git a/Client/src/Utils/ReadMe.md b/Client/src/Utils/ReadMe.md index 005e00a06..6488752d1 100644 --- a/Client/src/Utils/ReadMe.md +++ b/Client/src/Utils/ReadMe.md @@ -1 +1 @@ -#Utils folder \ No newline at end of file +#Utils folder diff --git a/Client/src/Utils/Theme/darkTheme.js b/Client/src/Utils/Theme/darkTheme.js index 01be72de3..d182f0496 100644 --- a/Client/src/Utils/Theme/darkTheme.js +++ b/Client/src/Utils/Theme/darkTheme.js @@ -1,256 +1,255 @@ import { createTheme } from "@mui/material"; const text = { - primary: "#fafafa", - secondary: "#e6e6e6", - tertiary: "#a1a1aa", - accent: "#8e8e8f", - disabled: "rgba(172, 172, 172, 0.3)", + primary: "#fafafa", + secondary: "#e6e6e6", + tertiary: "#a1a1aa", + accent: "#8e8e8f", + disabled: "rgba(172, 172, 172, 0.3)", }; const background = { - main: "#151518", - alt: "#09090b", - fill: "#2D2D33", - accent: "#18181a", + main: "#151518", + alt: "#09090b", + fill: "#2D2D33", + accent: "#18181a", }; const border = { light: "#27272a", dark: "#36363e" }; const fontFamilyDefault = - '"Inter","system-ui", "Avenir", "Helvetica", "Arial", sans-serif'; + '"Inter","system-ui", "Avenir", "Helvetica", "Arial", sans-serif'; const shadow = - "0px 4px 24px -4px rgba(255, 255, 255, 0.03), 0px 3px 3px -3px rgba(255, 255, 255, 0.01)"; + "0px 4px 24px -4px rgba(255, 255, 255, 0.03), 0px 3px 3px -3px rgba(255, 255, 255, 0.01)"; const darkTheme = createTheme({ - typography: { - fontFamily: fontFamilyDefault, - fontSize: 13, - h1: { fontSize: 22, color: text.primary, fontWeight: 500 }, - h2: { fontSize: 14.5, color: text.secondary, fontWeight: 400 }, - body1: { fontSize: 13, color: text.tertiary, fontWeight: 400 }, - body2: { fontSize: 12, color: text.tertiary, fontWeight: 400 }, - }, - palette: { - mode: "dark", - primary: { main: "#1570ef" }, - secondary: { main: "#2D2D33" }, - text: text, - background: background, - border: border, - info: { - text: text.primary, - main: text.secondary, - bg: background.main, - light: background.main, - border: border.light, - }, - success: { - text: "#079455", - main: "#45bb7a", - light: "#1c4428", - bg: "#12261e", - }, - error: { - text: "#f04438", - main: "#d32f2f", - light: "#542426", - bg: "#301a1f", - dark: "#932020", - border: "#f04438", - }, - warning: { - text: "#e88c30", - main: "#FF9F00", - light: "#624711", - bg: "#262115", - border: "#e88c30", - }, - percentage: { - uptimePoor: "#d32f2f", - uptimeFair: "#e88c30", - uptimeGood: "#ffd600", - uptimeExcellent: "#079455", - }, - unresolved: { main: "#664eff", light: "#3a1bff", bg: "#f2f4f7" }, - divider: border.light, - other: { - icon: "#e6e6e6", - line: "#27272a", - fill: "#18181a", - grid: "#454546", - autofill: "#2d2d33", - }, - }, - spacing: 2, - components: { - MuiCssBaseline: { - styleOverrides: { - body: { - backgroundImage: - "radial-gradient(circle, #09090b, #0c0c0e, #0f0f11, #111113, #131315, #131315, #131315, #131315, #111113, #0f0f11, #0c0c0e, #09090b)", - lineHeight: "inherit", - paddingLeft: "calc(100vw - 100%)", - }, - }, - }, - MuiButton: { - defaultProps: { - disableRipple: true, - }, - styleOverrides: { - root: ({ theme }) => ({ - variants: [ - { - props: (props) => props.variant === "group", - style: { - color: theme.palette.secondary.contrastText, - backgroundColor: theme.palette.background.main, - border: 1, - borderStyle: "solid", - borderColor: theme.palette.border.light, - }, - }, - { - props: (props) => - props.variant === "group" && props.filled === "true", - style: { - backgroundColor: theme.palette.secondary.main, - }, - }, - { - props: (props) => - props.variant === "contained" && props.color === "secondary", - style: { - border: 1, - borderStyle: "solid", - borderColor: theme.palette.border.dark, - }, - }, - ], - fontWeight: 400, - borderRadius: 4, - boxShadow: "none", - textTransform: "none", - "&:focus": { - outline: "none", - }, - "&:hover": { - boxShadow: "none", - }, - }), - }, - }, - MuiIconButton: { - styleOverrides: { - root: { - padding: 4, - transition: "none", - "&:hover": { - backgroundColor: border.light, - }, - }, - }, - }, - MuiPaper: { - styleOverrides: { - root: { - marginTop: 4, - padding: 0, - border: 1, - borderStyle: "solid", - borderColor: border.light, - borderRadius: 4, - boxShadow: shadow, - backgroundColor: background.main, - backgroundImage: "none", - }, - }, - }, - MuiList: { - styleOverrides: { - root: { - padding: 0, - }, - }, - }, - MuiListItemButton: { - styleOverrides: { - root: { - transition: "none", - }, - }, - }, - MuiMenuItem: { - styleOverrides: { - root: { - borderRadius: 4, - backgroundColor: "inherit", - padding: "4px 6px", - color: text.secondary, - fontSize: 13, - margin: 2, - minWidth: 100, - "&:hover, &.Mui-selected, &.Mui-selected:hover, &.Mui-selected.Mui-focusVisible": - { - backgroundColor: background.fill, - }, - }, - }, - }, - MuiTableCell: { - styleOverrides: { - root: { - borderBottomColor: border.light, - }, - }, - }, - MuiTableHead: { - styleOverrides: { - root: { - backgroundColor: background.accent, - }, - }, - }, - MuiPagination: { - styleOverrides: { - root: { - backgroundColor: background.main, - border: 1, - borderStyle: "solid", - borderColor: border.light, - "& button": { - color: text.tertiary, - borderRadius: 4, - }, - "& li:first-of-type button, & li:last-of-type button": { - border: 1, - borderStyle: "solid", - borderColor: border.light, - }, - }, - }, - }, - MuiPaginationItem: { - styleOverrides: { - root: { - "&:not(.MuiPaginationItem-ellipsis):hover, &.Mui-selected": { - backgroundColor: background.fill, - }, - }, - }, - }, - MuiSkeleton: { - styleOverrides: { - root: { - backgroundColor: "#151518", - }, - }, - }, - }, - shape: { - borderRadius: 2, - borderThick: 2, - boxShadow: shadow, - }, + typography: { + fontFamily: fontFamilyDefault, + fontSize: 13, + h1: { fontSize: 22, color: text.primary, fontWeight: 500 }, + h2: { fontSize: 14.5, color: text.secondary, fontWeight: 400 }, + body1: { fontSize: 13, color: text.tertiary, fontWeight: 400 }, + body2: { fontSize: 12, color: text.tertiary, fontWeight: 400 }, + }, + palette: { + mode: "dark", + primary: { main: "#1570ef" }, + secondary: { main: "#2D2D33" }, + text: text, + background: background, + border: border, + info: { + text: text.primary, + main: text.secondary, + bg: background.main, + light: background.main, + border: border.light, + }, + success: { + text: "#079455", + main: "#45bb7a", + light: "#1c4428", + bg: "#12261e", + }, + error: { + text: "#f04438", + main: "#d32f2f", + light: "#542426", + bg: "#301a1f", + dark: "#932020", + border: "#f04438", + }, + warning: { + text: "#e88c30", + main: "#FF9F00", + light: "#624711", + bg: "#262115", + border: "#e88c30", + }, + percentage: { + uptimePoor: "#d32f2f", + uptimeFair: "#e88c30", + uptimeGood: "#ffd600", + uptimeExcellent: "#079455", + }, + unresolved: { main: "#664eff", light: "#3a1bff", bg: "#f2f4f7" }, + divider: border.light, + other: { + icon: "#e6e6e6", + line: "#27272a", + fill: "#18181a", + grid: "#454546", + autofill: "#2d2d33", + }, + }, + spacing: 2, + components: { + MuiCssBaseline: { + styleOverrides: { + body: { + backgroundImage: + "radial-gradient(circle, #09090b, #0c0c0e, #0f0f11, #111113, #131315, #131315, #131315, #131315, #111113, #0f0f11, #0c0c0e, #09090b)", + lineHeight: "inherit", + paddingLeft: "calc(100vw - 100%)", + }, + }, + }, + MuiButton: { + defaultProps: { + disableRipple: true, + }, + styleOverrides: { + root: ({ theme }) => ({ + variants: [ + { + props: (props) => props.variant === "group", + style: { + color: theme.palette.secondary.contrastText, + backgroundColor: theme.palette.background.main, + border: 1, + borderStyle: "solid", + borderColor: theme.palette.border.light, + }, + }, + { + props: (props) => props.variant === "group" && props.filled === "true", + style: { + backgroundColor: theme.palette.secondary.main, + }, + }, + { + props: (props) => + props.variant === "contained" && props.color === "secondary", + style: { + border: 1, + borderStyle: "solid", + borderColor: theme.palette.border.dark, + }, + }, + ], + fontWeight: 400, + borderRadius: 4, + boxShadow: "none", + textTransform: "none", + "&:focus": { + outline: "none", + }, + "&:hover": { + boxShadow: "none", + }, + }), + }, + }, + MuiIconButton: { + styleOverrides: { + root: { + padding: 4, + transition: "none", + "&:hover": { + backgroundColor: border.light, + }, + }, + }, + }, + MuiPaper: { + styleOverrides: { + root: { + marginTop: 4, + padding: 0, + border: 1, + borderStyle: "solid", + borderColor: border.light, + borderRadius: 4, + boxShadow: shadow, + backgroundColor: background.main, + backgroundImage: "none", + }, + }, + }, + MuiList: { + styleOverrides: { + root: { + padding: 0, + }, + }, + }, + MuiListItemButton: { + styleOverrides: { + root: { + transition: "none", + }, + }, + }, + MuiMenuItem: { + styleOverrides: { + root: { + borderRadius: 4, + backgroundColor: "inherit", + padding: "4px 6px", + color: text.secondary, + fontSize: 13, + margin: 2, + minWidth: 100, + "&:hover, &.Mui-selected, &.Mui-selected:hover, &.Mui-selected.Mui-focusVisible": + { + backgroundColor: background.fill, + }, + }, + }, + }, + MuiTableCell: { + styleOverrides: { + root: { + borderBottomColor: border.light, + }, + }, + }, + MuiTableHead: { + styleOverrides: { + root: { + backgroundColor: background.accent, + }, + }, + }, + MuiPagination: { + styleOverrides: { + root: { + backgroundColor: background.main, + border: 1, + borderStyle: "solid", + borderColor: border.light, + "& button": { + color: text.tertiary, + borderRadius: 4, + }, + "& li:first-of-type button, & li:last-of-type button": { + border: 1, + borderStyle: "solid", + borderColor: border.light, + }, + }, + }, + }, + MuiPaginationItem: { + styleOverrides: { + root: { + "&:not(.MuiPaginationItem-ellipsis):hover, &.Mui-selected": { + backgroundColor: background.fill, + }, + }, + }, + }, + MuiSkeleton: { + styleOverrides: { + root: { + backgroundColor: "#151518", + }, + }, + }, + }, + shape: { + borderRadius: 2, + borderThick: 2, + boxShadow: shadow, + }, }); export default darkTheme; diff --git a/Client/src/Utils/Theme/lightTheme.js b/Client/src/Utils/Theme/lightTheme.js index 5d8d76cde..d2bfafaf4 100644 --- a/Client/src/Utils/Theme/lightTheme.js +++ b/Client/src/Utils/Theme/lightTheme.js @@ -1,253 +1,252 @@ import { createTheme } from "@mui/material"; const text = { - primary: "#1c2130", - secondary: "#344054", - tertiary: "#475467", - accent: "#838c99", + primary: "#1c2130", + secondary: "#344054", + tertiary: "#475467", + accent: "#838c99", }; const background = { - main: "#FFFFFF", - alt: "#FCFCFD", - fill: "#F4F4F4", - accent: "#f9fafb", + main: "#FFFFFF", + alt: "#FCFCFD", + fill: "#F4F4F4", + accent: "#f9fafb", }; const border = { light: "#eaecf0", dark: "#d0d5dd" }; const fontFamilyDefault = - '"Inter","system-ui", "Avenir", "Helvetica", "Arial", sans-serif'; + '"Inter","system-ui", "Avenir", "Helvetica", "Arial", sans-serif'; const shadow = - "0px 4px 24px -4px rgba(16, 24, 40, 0.08), 0px 3px 3px -3px rgba(16, 24, 40, 0.03)"; + "0px 4px 24px -4px rgba(16, 24, 40, 0.08), 0px 3px 3px -3px rgba(16, 24, 40, 0.03)"; const lightTheme = createTheme({ - typography: { - fontFamily: fontFamilyDefault, - fontSize: 13, - h1: { fontSize: 22, color: text.primary, fontWeight: 500 }, - h2: { fontSize: 14.5, color: text.secondary, fontWeight: 400 }, - body1: { fontSize: 13, color: text.tertiary, fontWeight: 400 }, - body2: { fontSize: 12, color: text.tertiary, fontWeight: 400 }, - }, - palette: { - primary: { main: "#1570EF" }, - secondary: { main: "#F4F4F4", dark: "#e3e3e3", contrastText: "#475467" }, - text: text, - background: background, - border: border, - info: { - text: text.primary, - main: text.tertiary, - bg: background.main, - light: background.main, - border: border.dark, - }, - success: { - text: "#079455", - main: "#17b26a", - light: "#d4f4e1", - bg: "#ecfdf3", - }, - error: { - text: "#f04438", - main: "#d32f2f", - light: "#fbd1d1", - bg: "#f9eced", - border: "#f04438", - }, - warning: { - text: "#DC6803", - main: "#fdb022", - light: "#ffecbc", - bg: "#fef8ea", - border: "#fec84b", - }, - percentage: { - uptimePoor: "#d32f2f", - uptimeFair: "#ec8013", - uptimeGood: "#ffb800", - uptimeExcellent: "#079455", - }, - unresolved: { main: "#4e5ba6", light: "#e2eaf7", bg: "#f2f4f7" }, - divider: border.light, - other: { - icon: "#667085", - line: "#d6d9dd", - fill: "#e3e3e3", - grid: "#a2a3a3", - autofill: "#e8f0fe", - }, - }, - spacing: 2, - components: { - MuiCssBaseline: { - styleOverrides: { - body: { - backgroundImage: - "radial-gradient(circle, #fcfcfd, #fdfcfd, #fdfdfd, #fefdfe, #fefefe, #fefefe, #fefefe, #fefefe, #fefdfe, #fdfdfd, #fdfcfd, #fcfcfd)", - lineHeight: "inherit", - paddingLeft: "calc(100vw - 100%)", - }, - }, - }, - MuiButton: { - defaultProps: { - disableRipple: true, - }, - styleOverrides: { - root: ({ theme }) => ({ - variants: [ - { - props: (props) => props.variant === "group", - style: { - color: theme.palette.secondary.contrastText, - backgroundColor: theme.palette.background.main, - border: 1, - borderStyle: "solid", - borderColor: theme.palette.border.light, - }, - }, - { - props: (props) => - props.variant === "group" && props.filled === "true", - style: { - backgroundColor: theme.palette.secondary.main, - }, - }, - { - props: (props) => - props.variant === "contained" && props.color === "secondary", - style: { - border: 1, - borderStyle: "solid", - borderColor: theme.palette.border.light, - }, - }, - ], - fontWeight: 400, - borderRadius: 4, - boxShadow: "none", - textTransform: "none", - "&:focus": { - outline: "none", - }, - "&:hover": { - boxShadow: "none", - }, - }), - }, - }, - MuiIconButton: { - styleOverrides: { - root: { - padding: 4, - transition: "none", - "&:hover": { - backgroundColor: background.fill, - }, - }, - }, - }, - MuiPaper: { - styleOverrides: { - root: { - marginTop: 4, - padding: 0, - border: 1, - borderStyle: "solid", - borderColor: border.light, - borderRadius: 4, - boxShadow: shadow, - backgroundColor: background.main, - backgroundImage: "none", - }, - }, - }, - MuiList: { - styleOverrides: { - root: { - padding: 0, - }, - }, - }, - MuiListItemButton: { - styleOverrides: { - root: { - transition: "none", - }, - }, - }, - MuiMenuItem: { - styleOverrides: { - root: { - borderRadius: 4, - backgroundColor: "inherit", - padding: "4px 6px", - color: text.secondary, - fontSize: 13, - margin: 2, - minWidth: 100, - "&:hover, &.Mui-selected, &.Mui-selected:hover, &.Mui-selected.Mui-focusVisible": - { - backgroundColor: background.fill, - }, - }, - }, - }, - MuiTableCell: { - styleOverrides: { - root: { - borderBottomColor: border.light, - }, - }, - }, - MuiTableHead: { - styleOverrides: { - root: { - backgroundColor: background.accent, - }, - }, - }, - MuiPagination: { - styleOverrides: { - root: { - backgroundColor: background.main, - border: 1, - borderStyle: "solid", - borderColor: border.light, - "& button": { - color: text.tertiary, - borderRadius: 4, - }, - "& li:first-of-type button, & li:last-of-type button": { - border: 1, - borderStyle: "solid", - borderColor: border.light, - }, - }, - }, - }, - MuiPaginationItem: { - styleOverrides: { - root: { - "&:not(.MuiPaginationItem-ellipsis):hover, &.Mui-selected": { - backgroundColor: background.fill, - }, - }, - }, - }, - MuiSkeleton: { - styleOverrides: { - root: { - backgroundColor: "#f2f4f7", - }, - }, - }, - }, - shape: { - borderRadius: 2, - borderThick: 2, - boxShadow: shadow, - }, + typography: { + fontFamily: fontFamilyDefault, + fontSize: 13, + h1: { fontSize: 22, color: text.primary, fontWeight: 500 }, + h2: { fontSize: 14.5, color: text.secondary, fontWeight: 400 }, + body1: { fontSize: 13, color: text.tertiary, fontWeight: 400 }, + body2: { fontSize: 12, color: text.tertiary, fontWeight: 400 }, + }, + palette: { + primary: { main: "#1570EF" }, + secondary: { main: "#F4F4F4", dark: "#e3e3e3", contrastText: "#475467" }, + text: text, + background: background, + border: border, + info: { + text: text.primary, + main: text.tertiary, + bg: background.main, + light: background.main, + border: border.dark, + }, + success: { + text: "#079455", + main: "#17b26a", + light: "#d4f4e1", + bg: "#ecfdf3", + }, + error: { + text: "#f04438", + main: "#d32f2f", + light: "#fbd1d1", + bg: "#f9eced", + border: "#f04438", + }, + warning: { + text: "#DC6803", + main: "#fdb022", + light: "#ffecbc", + bg: "#fef8ea", + border: "#fec84b", + }, + percentage: { + uptimePoor: "#d32f2f", + uptimeFair: "#ec8013", + uptimeGood: "#ffb800", + uptimeExcellent: "#079455", + }, + unresolved: { main: "#4e5ba6", light: "#e2eaf7", bg: "#f2f4f7" }, + divider: border.light, + other: { + icon: "#667085", + line: "#d6d9dd", + fill: "#e3e3e3", + grid: "#a2a3a3", + autofill: "#e8f0fe", + }, + }, + spacing: 2, + components: { + MuiCssBaseline: { + styleOverrides: { + body: { + backgroundImage: + "radial-gradient(circle, #fcfcfd, #fdfcfd, #fdfdfd, #fefdfe, #fefefe, #fefefe, #fefefe, #fefefe, #fefdfe, #fdfdfd, #fdfcfd, #fcfcfd)", + lineHeight: "inherit", + paddingLeft: "calc(100vw - 100%)", + }, + }, + }, + MuiButton: { + defaultProps: { + disableRipple: true, + }, + styleOverrides: { + root: ({ theme }) => ({ + variants: [ + { + props: (props) => props.variant === "group", + style: { + color: theme.palette.secondary.contrastText, + backgroundColor: theme.palette.background.main, + border: 1, + borderStyle: "solid", + borderColor: theme.palette.border.light, + }, + }, + { + props: (props) => props.variant === "group" && props.filled === "true", + style: { + backgroundColor: theme.palette.secondary.main, + }, + }, + { + props: (props) => + props.variant === "contained" && props.color === "secondary", + style: { + border: 1, + borderStyle: "solid", + borderColor: theme.palette.border.light, + }, + }, + ], + fontWeight: 400, + borderRadius: 4, + boxShadow: "none", + textTransform: "none", + "&:focus": { + outline: "none", + }, + "&:hover": { + boxShadow: "none", + }, + }), + }, + }, + MuiIconButton: { + styleOverrides: { + root: { + padding: 4, + transition: "none", + "&:hover": { + backgroundColor: background.fill, + }, + }, + }, + }, + MuiPaper: { + styleOverrides: { + root: { + marginTop: 4, + padding: 0, + border: 1, + borderStyle: "solid", + borderColor: border.light, + borderRadius: 4, + boxShadow: shadow, + backgroundColor: background.main, + backgroundImage: "none", + }, + }, + }, + MuiList: { + styleOverrides: { + root: { + padding: 0, + }, + }, + }, + MuiListItemButton: { + styleOverrides: { + root: { + transition: "none", + }, + }, + }, + MuiMenuItem: { + styleOverrides: { + root: { + borderRadius: 4, + backgroundColor: "inherit", + padding: "4px 6px", + color: text.secondary, + fontSize: 13, + margin: 2, + minWidth: 100, + "&:hover, &.Mui-selected, &.Mui-selected:hover, &.Mui-selected.Mui-focusVisible": + { + backgroundColor: background.fill, + }, + }, + }, + }, + MuiTableCell: { + styleOverrides: { + root: { + borderBottomColor: border.light, + }, + }, + }, + MuiTableHead: { + styleOverrides: { + root: { + backgroundColor: background.accent, + }, + }, + }, + MuiPagination: { + styleOverrides: { + root: { + backgroundColor: background.main, + border: 1, + borderStyle: "solid", + borderColor: border.light, + "& button": { + color: text.tertiary, + borderRadius: 4, + }, + "& li:first-of-type button, & li:last-of-type button": { + border: 1, + borderStyle: "solid", + borderColor: border.light, + }, + }, + }, + }, + MuiPaginationItem: { + styleOverrides: { + root: { + "&:not(.MuiPaginationItem-ellipsis):hover, &.Mui-selected": { + backgroundColor: background.fill, + }, + }, + }, + }, + MuiSkeleton: { + styleOverrides: { + root: { + backgroundColor: "#f2f4f7", + }, + }, + }, + }, + shape: { + borderRadius: 2, + borderThick: 2, + boxShadow: shadow, + }, }); export default lightTheme; diff --git a/Client/src/Utils/debounce.jsx b/Client/src/Utils/debounce.jsx index a89f8c687..11d639f14 100644 --- a/Client/src/Utils/debounce.jsx +++ b/Client/src/Utils/debounce.jsx @@ -1,18 +1,18 @@ import { useState, useEffect } from "react"; const useDebounce = (value, delay) => { - const [debouncedValue, setDebouncedValue] = useState(value); + const [debouncedValue, setDebouncedValue] = useState(value); - useEffect(() => { - const handler = setTimeout(() => { - setDebouncedValue(value); - }, delay); + useEffect(() => { + const handler = setTimeout(() => { + setDebouncedValue(value); + }, delay); - return () => { - clearTimeout(handler); - }; - }, [value, delay]); - return debouncedValue; + return () => { + clearTimeout(handler); + }; + }, [value, delay]); + return debouncedValue; }; export default useDebounce; diff --git a/Client/src/Utils/fileUtils.js b/Client/src/Utils/fileUtils.js index 699832b47..1c35738dd 100644 --- a/Client/src/Utils/fileUtils.js +++ b/Client/src/Utils/fileUtils.js @@ -1,11 +1,11 @@ export const formatBytes = (bytes) => { - if (bytes === 0) return "0 Bytes"; - const megabytes = bytes / (1024 * 1024); - return megabytes.toFixed(2) + " MB"; + if (bytes === 0) return "0 Bytes"; + const megabytes = bytes / (1024 * 1024); + return megabytes.toFixed(2) + " MB"; }; export const checkImage = (url) => { - const img = new Image(); - img.src = url; - return img.naturalWidth !== 0; -}; \ No newline at end of file + const img = new Image(); + img.src = url; + return img.naturalWidth !== 0; +}; diff --git a/Client/src/Utils/greeting.jsx b/Client/src/Utils/greeting.jsx index 64a85bd65..5b45812a5 100644 --- a/Client/src/Utils/greeting.jsx +++ b/Client/src/Utils/greeting.jsx @@ -6,115 +6,115 @@ import { useEffect } from "react"; import { setGreeting } from "../Features/UI/uiSlice"; const early = [ - { - prepend: "Rise and shine", - append: "If you’re up this early, you might as well be a legend!", - emoji: "☕", - }, - { - prepend: "Good morning", - append: "The world’s still asleep, but you’re already awesome!", - emoji: "🦉", - }, - { - prepend: "Good morning", - append: "Are you a wizard? Only magical people are up at this hour!", - emoji: "🌄", - }, - { - prepend: "Up before the roosters", - append: "Ready to tackle the day before it even starts?", - emoji: "🐓", - }, - { - prepend: "Early bird special", - append: "Let’s get things done while everyone else is snoozing!", - emoji: "🌟", - }, + { + prepend: "Rise and shine", + append: "If you’re up this early, you might as well be a legend!", + emoji: "☕", + }, + { + prepend: "Good morning", + append: "The world’s still asleep, but you’re already awesome!", + emoji: "🦉", + }, + { + prepend: "Good morning", + append: "Are you a wizard? Only magical people are up at this hour!", + emoji: "🌄", + }, + { + prepend: "Up before the roosters", + append: "Ready to tackle the day before it even starts?", + emoji: "🐓", + }, + { + prepend: "Early bird special", + append: "Let’s get things done while everyone else is snoozing!", + emoji: "🌟", + }, ]; const morning = [ - { - prepend: "Good morning", - append: "Is it coffee o’clock yet, or should we start with high fives?", - emoji: "☕", - }, - { - prepend: "Morning", - append: "The sun is up, and so are you—time to be amazing!", - emoji: "🌞", - }, - { - prepend: "Good morning", - append: "Time to make today the best thing since sliced bread!", - emoji: "🥐", - }, - { - prepend: "Morning", - append: "Let’s kick off the day with more energy than a double espresso!", - emoji: "🚀", - }, - { - prepend: "Rise and shine", - append: "You’re about to make today so great, even Monday will be jealous!", - emoji: "🌟", - }, + { + prepend: "Good morning", + append: "Is it coffee o’clock yet, or should we start with high fives?", + emoji: "☕", + }, + { + prepend: "Morning", + append: "The sun is up, and so are you—time to be amazing!", + emoji: "🌞", + }, + { + prepend: "Good morning", + append: "Time to make today the best thing since sliced bread!", + emoji: "🥐", + }, + { + prepend: "Morning", + append: "Let’s kick off the day with more energy than a double espresso!", + emoji: "🚀", + }, + { + prepend: "Rise and shine", + append: "You’re about to make today so great, even Monday will be jealous!", + emoji: "🌟", + }, ]; const afternoon = [ - { - prepend: "Good afternoon", - append: "How about a break to celebrate how awesome you’re doing?", - emoji: "🥪", - }, - { - prepend: "Afternoon", - append: "If you’re still going strong, you’re officially a rockstar!", - emoji: "🌞", - }, - { - prepend: "Hey there", - append: "The afternoon is your playground—let’s make it epic!", - emoji: "🍕", - }, - { - prepend: "Good afternoon", - append: "Time to crush the rest of the day like a pro!", - emoji: "🏆", - }, - { - prepend: "Afternoon", - append: "Time to turn those afternoon slumps into afternoon triumphs!", - emoji: "🎉", - }, + { + prepend: "Good afternoon", + append: "How about a break to celebrate how awesome you’re doing?", + emoji: "🥪", + }, + { + prepend: "Afternoon", + append: "If you’re still going strong, you’re officially a rockstar!", + emoji: "🌞", + }, + { + prepend: "Hey there", + append: "The afternoon is your playground—let’s make it epic!", + emoji: "🍕", + }, + { + prepend: "Good afternoon", + append: "Time to crush the rest of the day like a pro!", + emoji: "🏆", + }, + { + prepend: "Afternoon", + append: "Time to turn those afternoon slumps into afternoon triumphs!", + emoji: "🎉", + }, ]; const evening = [ - { - prepend: "Good evening", - append: "Time to wind down and think about how you crushed today!", - emoji: "🌇", - }, - { - prepend: "Evening", - append: "You’ve earned a break—let’s make the most of these evening vibes!", - emoji: "🍹", - }, - { - prepend: "Hey there", - append: "Time to relax and bask in the glow of your day’s awesomeness!", - emoji: "🌙", - }, - { - prepend: "Good evening", - append: "Ready to trade productivity for chill mode?", - emoji: "🛋️ ", - }, - { - prepend: "Evening", - append: "Let’s call it a day and toast to your success!", - emoji: "🕶️", - }, + { + prepend: "Good evening", + append: "Time to wind down and think about how you crushed today!", + emoji: "🌇", + }, + { + prepend: "Evening", + append: "You’ve earned a break—let’s make the most of these evening vibes!", + emoji: "🍹", + }, + { + prepend: "Hey there", + append: "Time to relax and bask in the glow of your day’s awesomeness!", + emoji: "🌙", + }, + { + prepend: "Good evening", + append: "Ready to trade productivity for chill mode?", + emoji: "🛋️ ", + }, + { + prepend: "Evening", + append: "Let’s call it a day and toast to your success!", + emoji: "🕶️", + }, ]; /** @@ -131,52 +131,63 @@ const evening = [ */ const Greeting = ({ type = "" }) => { - const theme = useTheme(); - const dispatch = useDispatch(); - const { firstName } = useSelector((state) => state.auth.user); - const index = useSelector((state) => state.ui.greeting.index); - const lastUpdate = useSelector((state) => state.ui.greeting.lastUpdate); + const theme = useTheme(); + const dispatch = useDispatch(); + const { firstName } = useSelector((state) => state.auth.user); + const index = useSelector((state) => state.ui.greeting.index); + const lastUpdate = useSelector((state) => state.ui.greeting.lastUpdate); - const now = new Date(); - const hour = now.getHours(); + const now = new Date(); + const hour = now.getHours(); - useEffect(() => { - const hourDiff = lastUpdate ? hour - lastUpdate : null; + useEffect(() => { + const hourDiff = lastUpdate ? hour - lastUpdate : null; - if (!lastUpdate || hourDiff >= 1) { - let random = Math.floor(Math.random() * 5); - dispatch(setGreeting({ index: random, lastUpdate: hour })); - } - }, [dispatch, hour]); + if (!lastUpdate || hourDiff >= 1) { + let random = Math.floor(Math.random() * 5); + dispatch(setGreeting({ index: random, lastUpdate: hour })); + } + }, [dispatch, hour]); - let greetingArray = - hour < 6 ? early : hour < 12 ? morning : hour < 18 ? afternoon : evening; - const { prepend, append, emoji } = greetingArray[index]; + let greetingArray = + hour < 6 ? early : hour < 12 ? morning : hour < 18 ? afternoon : evening; + const { prepend, append, emoji } = greetingArray[index]; - return ( - - - - {prepend},{" "} - - - {firstName} {emoji} - - - - {append} — Here’s an overview of your {type} monitors. - - - ); + return ( + + + + {prepend},{" "} + + + {firstName} {emoji} + + + + {append} — Here’s an overview of your {type} monitors. + + + ); }; Greeting.propTypes = { - type: PropTypes.string, + type: PropTypes.string, }; export default Greeting; diff --git a/Client/src/Utils/monitorUtils.js b/Client/src/Utils/monitorUtils.js index 5ea5462e2..3e6b0116d 100644 --- a/Client/src/Utils/monitorUtils.js +++ b/Client/src/Utils/monitorUtils.js @@ -5,13 +5,13 @@ * @returns {number} Timestamp of the most recent check. */ export const getLastChecked = (checks, duration = true) => { - if (!checks || checks.length === 0) { - return 0; // Handle case when no checks are available - } + if (!checks || checks.length === 0) { + return 0; // Handle case when no checks are available + } - // Data is sorted newest -> oldest, so newest check is the most recent - if (!duration) { - return new Date(checks[0].createdAt); - } - return new Date() - new Date(checks[0].createdAt); + // Data is sorted newest -> oldest, so newest check is the most recent + if (!duration) { + return new Date(checks[0].createdAt); + } + return new Date() - new Date(checks[0].createdAt); }; diff --git a/Client/src/Utils/timeUtils.js b/Client/src/Utils/timeUtils.js index 87f85e37a..d4a0dce3b 100644 --- a/Client/src/Utils/timeUtils.js +++ b/Client/src/Utils/timeUtils.js @@ -11,85 +11,85 @@ export const MS_PER_DAY = 24 * MS_PER_HOUR; export const MS_PER_WEEK = MS_PER_DAY * 7; export const formatDuration = (ms) => { - const seconds = Math.floor(ms / 1000); - const minutes = Math.floor(seconds / 60); - const hours = Math.floor(minutes / 60); - const days = Math.floor(hours / 24); + const seconds = Math.floor(ms / 1000); + const minutes = Math.floor(seconds / 60); + const hours = Math.floor(minutes / 60); + const days = Math.floor(hours / 24); - let dateStr = ""; + let dateStr = ""; - days && (dateStr += `${days}d `); - hours && (dateStr += `${hours % 24}h `); - minutes && (dateStr += `${minutes % 60}m `); - seconds && (dateStr += `${seconds % 60}s `); + days && (dateStr += `${days}d `); + hours && (dateStr += `${hours % 24}h `); + minutes && (dateStr += `${minutes % 60}m `); + seconds && (dateStr += `${seconds % 60}s `); - dateStr === "" && (dateStr = "0s"); + dateStr === "" && (dateStr = "0s"); - return dateStr; + return dateStr; }; export const formatDurationRounded = (ms) => { - const seconds = Math.floor(ms / 1000); - const minutes = Math.floor(seconds / 60); - const hours = Math.floor(minutes / 60); - const days = Math.floor(hours / 24); + const seconds = Math.floor(ms / 1000); + const minutes = Math.floor(seconds / 60); + const hours = Math.floor(minutes / 60); + const days = Math.floor(hours / 24); - let time = ""; - if (days > 0) { - time += `${days} day${days !== 1 ? "s" : ""}`; - return time; - } - if (hours > 0) { - time += `${hours} hour${hours !== 1 ? "s" : ""}`; - return time; - } - if (minutes > 0) { - time += `${minutes} minute${minutes !== 1 ? "s" : ""}`; - return time; - } - if (seconds > 0) { - time += `${seconds} second${seconds !== 1 ? "s" : ""}`; - return time; - } + let time = ""; + if (days > 0) { + time += `${days} day${days !== 1 ? "s" : ""}`; + return time; + } + if (hours > 0) { + time += `${hours} hour${hours !== 1 ? "s" : ""}`; + return time; + } + if (minutes > 0) { + time += `${minutes} minute${minutes !== 1 ? "s" : ""}`; + return time; + } + if (seconds > 0) { + time += `${seconds} second${seconds !== 1 ? "s" : ""}`; + return time; + } - return time; + return time; }; export const formatDurationSplit = (ms) => { - const seconds = Math.floor(ms / 1000); - const minutes = Math.floor(seconds / 60); - const hours = Math.floor(minutes / 60); - const days = Math.floor(hours / 24); + const seconds = Math.floor(ms / 1000); + const minutes = Math.floor(seconds / 60); + const hours = Math.floor(minutes / 60); + const days = Math.floor(hours / 24); - return days > 0 - ? { time: days, format: days === 1 ? "day" : "days" } - : hours > 0 - ? { time: hours, format: hours === 1 ? "hour" : "hours" } - : minutes > 0 - ? { time: minutes, format: minutes === 1 ? "minute" : "minutes" } - : seconds > 0 - ? { time: seconds, format: seconds === 1 ? "second" : "seconds" } - : { time: 0, format: "seconds" }; + return days > 0 + ? { time: days, format: days === 1 ? "day" : "days" } + : hours > 0 + ? { time: hours, format: hours === 1 ? "hour" : "hours" } + : minutes > 0 + ? { time: minutes, format: minutes === 1 ? "minute" : "minutes" } + : seconds > 0 + ? { time: seconds, format: seconds === 1 ? "second" : "seconds" } + : { time: 0, format: "seconds" }; }; export const formatDate = (date, customOptions) => { - const options = { - year: "numeric", - month: "long", - day: "numeric", - hour: "numeric", - minute: "numeric", - hour12: true, - ...customOptions, - }; + const options = { + year: "numeric", + month: "long", + day: "numeric", + hour: "numeric", + minute: "numeric", + hour12: true, + ...customOptions, + }; - // Return the date using the specified options - return date - .toLocaleString("en-US", options) - .replace(/\b(AM|PM)\b/g, (match) => match.toLowerCase()); + // Return the date using the specified options + return date + .toLocaleString("en-US", options) + .replace(/\b(AM|PM)\b/g, (match) => match.toLowerCase()); }; export const formatDateWithTz = (timestamp, format, timezone) => { - const formattedDate = dayjs(timestamp, timezone).tz(timezone).format(format); - return formattedDate; + const formattedDate = dayjs(timestamp, timezone).tz(timezone).format(format); + return formattedDate; }; diff --git a/Client/src/Utils/timezones.json b/Client/src/Utils/timezones.json index 361acbba7..607d44c0f 100644 --- a/Client/src/Utils/timezones.json +++ b/Client/src/Utils/timezones.json @@ -1,1698 +1,1698 @@ [ - { - "_id": "Africa/Abidjan", - "name": "Africa/Abidjan" - }, - { - "_id": "Africa/Accra", - "name": "Africa/Accra" - }, - { - "_id": "Africa/Addis_Ababa", - "name": "Africa/Addis_Ababa" - }, - { - "_id": "Africa/Algiers", - "name": "Africa/Algiers" - }, - { - "_id": "Africa/Asmara", - "name": "Africa/Asmara" - }, - { - "_id": "Africa/Bamako", - "name": "Africa/Bamako" - }, - { - "_id": "Africa/Bangui", - "name": "Africa/Bangui" - }, - { - "_id": "Africa/Banjul", - "name": "Africa/Banjul" - }, - { - "_id": "Africa/Bissau", - "name": "Africa/Bissau" - }, - { - "_id": "Africa/Blantyre", - "name": "Africa/Blantyre" - }, - { - "_id": "Africa/Brazzaville", - "name": "Africa/Brazzaville" - }, - { - "_id": "Africa/Bujumbura", - "name": "Africa/Bujumbura" - }, - { - "_id": "Africa/Cairo", - "name": "Africa/Cairo" - }, - { - "_id": "Africa/Casablanca", - "name": "Africa/Casablanca" - }, - { - "_id": "Africa/Ceuta", - "name": "Africa/Ceuta" - }, - { - "_id": "Africa/Conakry", - "name": "Africa/Conakry" - }, - { - "_id": "Africa/Dakar", - "name": "Africa/Dakar" - }, - { - "_id": "Africa/Dar_es_Salaam", - "name": "Africa/Dar_es_Salaam" - }, - { - "_id": "Africa/Djibouti", - "name": "Africa/Djibouti" - }, - { - "_id": "Africa/Douala", - "name": "Africa/Douala" - }, - { - "_id": "Africa/El_Aaiun", - "name": "Africa/El_Aaiun" - }, - { - "_id": "Africa/Freetown", - "name": "Africa/Freetown" - }, - { - "_id": "Africa/Gaborone", - "name": "Africa/Gaborone" - }, - { - "_id": "Africa/Harare", - "name": "Africa/Harare" - }, - { - "_id": "Africa/Johannesburg", - "name": "Africa/Johannesburg" - }, - { - "_id": "Africa/Juba", - "name": "Africa/Juba" - }, - { - "_id": "Africa/Kampala", - "name": "Africa/Kampala" - }, - { - "_id": "Africa/Khartoum", - "name": "Africa/Khartoum" - }, - { - "_id": "Africa/Kigali", - "name": "Africa/Kigali" - }, - { - "_id": "Africa/Kinshasa", - "name": "Africa/Kinshasa" - }, - { - "_id": "Africa/Lagos", - "name": "Africa/Lagos" - }, - { - "_id": "Africa/Libreville", - "name": "Africa/Libreville" - }, - { - "_id": "Africa/Lome", - "name": "Africa/Lome" - }, - { - "_id": "Africa/Luanda", - "name": "Africa/Luanda" - }, - { - "_id": "Africa/Lubumbashi", - "name": "Africa/Lubumbashi" - }, - { - "_id": "Africa/Lusaka", - "name": "Africa/Lusaka" - }, - { - "_id": "Africa/Malabo", - "name": "Africa/Malabo" - }, - { - "_id": "Africa/Maputo", - "name": "Africa/Maputo" - }, - { - "_id": "Africa/Maseru", - "name": "Africa/Maseru" - }, - { - "_id": "Africa/Mbabane", - "name": "Africa/Mbabane" - }, - { - "_id": "Africa/Mogadishu", - "name": "Africa/Mogadishu" - }, - { - "_id": "Africa/Monrovia", - "name": "Africa/Monrovia" - }, - { - "_id": "Africa/Nairobi", - "name": "Africa/Nairobi" - }, - { - "_id": "Africa/Ndjamena", - "name": "Africa/Ndjamena" - }, - { - "_id": "Africa/Niamey", - "name": "Africa/Niamey" - }, - { - "_id": "Africa/Nouakchott", - "name": "Africa/Nouakchott" - }, - { - "_id": "Africa/Ouagadougou", - "name": "Africa/Ouagadougou" - }, - { - "_id": "Africa/Porto-Novo", - "name": "Africa/Porto-Novo" - }, - { - "_id": "Africa/Sao_Tome", - "name": "Africa/Sao_Tome" - }, - { - "_id": "Africa/Tripoli", - "name": "Africa/Tripoli" - }, - { - "_id": "Africa/Tunis", - "name": "Africa/Tunis" - }, - { - "_id": "Africa/Windhoek", - "name": "Africa/Windhoek" - }, - { - "_id": "America/Adak", - "name": "America/Adak" - }, - { - "_id": "America/Anchorage", - "name": "America/Anchorage" - }, - { - "_id": "America/Anguilla", - "name": "America/Anguilla" - }, - { - "_id": "America/Antigua", - "name": "America/Antigua" - }, - { - "_id": "America/Araguaina", - "name": "America/Araguaina" - }, - { - "_id": "America/Argentina/Buenos_Aires", - "name": "America/Argentina/Buenos_Aires" - }, - { - "_id": "America/Argentina/Catamarca", - "name": "America/Argentina/Catamarca" - }, - { - "_id": "America/Argentina/Cordoba", - "name": "America/Argentina/Cordoba" - }, - { - "_id": "America/Argentina/Jujuy", - "name": "America/Argentina/Jujuy" - }, - { - "_id": "America/Argentina/La_Rioja", - "name": "America/Argentina/La_Rioja" - }, - { - "_id": "America/Argentina/Mendoza", - "name": "America/Argentina/Mendoza" - }, - { - "_id": "America/Argentina/Rio_Gallegos", - "name": "America/Argentina/Rio_Gallegos" - }, - { - "_id": "America/Argentina/Salta", - "name": "America/Argentina/Salta" - }, - { - "_id": "America/Argentina/San_Juan", - "name": "America/Argentina/San_Juan" - }, - { - "_id": "America/Argentina/San_Luis", - "name": "America/Argentina/San_Luis" - }, - { - "_id": "America/Argentina/Tucuman", - "name": "America/Argentina/Tucuman" - }, - { - "_id": "America/Argentina/Ushuaia", - "name": "America/Argentina/Ushuaia" - }, - { - "_id": "America/Aruba", - "name": "America/Aruba" - }, - { - "_id": "America/Asuncion", - "name": "America/Asuncion" - }, - { - "_id": "America/Atikokan", - "name": "America/Atikokan" - }, - { - "_id": "America/Bahia", - "name": "America/Bahia" - }, - { - "_id": "America/Bahia_Banderas", - "name": "America/Bahia_Banderas" - }, - { - "_id": "America/Barbados", - "name": "America/Barbados" - }, - { - "_id": "America/Belem", - "name": "America/Belem" - }, - { - "_id": "America/Belize", - "name": "America/Belize" - }, - { - "_id": "America/Blanc-Sablon", - "name": "America/Blanc-Sablon" - }, - { - "_id": "America/Boa_Vista", - "name": "America/Boa_Vista" - }, - { - "_id": "America/Bogota", - "name": "America/Bogota" - }, - { - "_id": "America/Boise", - "name": "America/Boise" - }, - { - "_id": "America/Cambridge_Bay", - "name": "America/Cambridge_Bay" - }, - { - "_id": "America/Campo_Grande", - "name": "America/Campo_Grande" - }, - { - "_id": "America/Cancun", - "name": "America/Cancun" - }, - { - "_id": "America/Caracas", - "name": "America/Caracas" - }, - { - "_id": "America/Cayenne", - "name": "America/Cayenne" - }, - { - "_id": "America/Cayman", - "name": "America/Cayman" - }, - { - "_id": "America/Chicago", - "name": "America/Chicago" - }, - { - "_id": "America/Chihuahua", - "name": "America/Chihuahua" - }, - { - "_id": "America/Costa_Rica", - "name": "America/Costa_Rica" - }, - { - "_id": "America/Creston", - "name": "America/Creston" - }, - { - "_id": "America/Cuiaba", - "name": "America/Cuiaba" - }, - { - "_id": "America/Curacao", - "name": "America/Curacao" - }, - { - "_id": "America/Danmarkshavn", - "name": "America/Danmarkshavn" - }, - { - "_id": "America/Dawson", - "name": "America/Dawson" - }, - { - "_id": "America/Dawson_Creek", - "name": "America/Dawson_Creek" - }, - { - "_id": "America/Denver", - "name": "America/Denver" - }, - { - "_id": "America/Detroit", - "name": "America/Detroit" - }, - { - "_id": "America/Dominica", - "name": "America/Dominica" - }, - { - "_id": "America/Edmonton", - "name": "America/Edmonton" - }, - { - "_id": "America/Eirunepe", - "name": "America/Eirunepe" - }, - { - "_id": "America/El_Salvador", - "name": "America/El_Salvador" - }, - { - "_id": "America/Fort_Nelson", - "name": "America/Fort_Nelson" - }, - { - "_id": "America/Fortaleza", - "name": "America/Fortaleza" - }, - { - "_id": "America/Glace_Bay", - "name": "America/Glace_Bay" - }, - { - "_id": "America/Godthab", - "name": "America/Godthab" - }, - { - "_id": "America/Goose_Bay", - "name": "America/Goose_Bay" - }, - { - "_id": "America/Grand_Turk", - "name": "America/Grand_Turk" - }, - { - "_id": "America/Grenada", - "name": "America/Grenada" - }, - { - "_id": "America/Guadeloupe", - "name": "America/Guadeloupe" - }, - { - "_id": "America/Guatemala", - "name": "America/Guatemala" - }, - { - "_id": "America/Guayaquil", - "name": "America/Guayaquil" - }, - { - "_id": "America/Guyana", - "name": "America/Guyana" - }, - { - "_id": "America/Halifax", - "name": "America/Halifax" - }, - { - "_id": "America/Havana", - "name": "America/Havana" - }, - { - "_id": "America/Hermosillo", - "name": "America/Hermosillo" - }, - { - "_id": "America/Indiana/Indianapolis", - "name": "America/Indiana/Indianapolis" - }, - { - "_id": "America/Indiana/Knox", - "name": "America/Indiana/Knox" - }, - { - "_id": "America/Indiana/Marengo", - "name": "America/Indiana/Marengo" - }, - { - "_id": "America/Indiana/Petersburg", - "name": "America/Indiana/Petersburg" - }, - { - "_id": "America/Indiana/Tell_City", - "name": "America/Indiana/Tell_City" - }, - { - "_id": "America/Indiana/Vevay", - "name": "America/Indiana/Vevay" - }, - { - "_id": "America/Indiana/Vincennes", - "name": "America/Indiana/Vincennes" - }, - { - "_id": "America/Indiana/Winamac", - "name": "America/Indiana/Winamac" - }, - { - "_id": "America/Inuvik", - "name": "America/Inuvik" - }, - { - "_id": "America/Iqaluit", - "name": "America/Iqaluit" - }, - { - "_id": "America/Jamaica", - "name": "America/Jamaica" - }, - { - "_id": "America/Juneau", - "name": "America/Juneau" - }, - { - "_id": "America/Kentucky/Louisville", - "name": "America/Kentucky/Louisville" - }, - { - "_id": "America/Kentucky/Monticello", - "name": "America/Kentucky/Monticello" - }, - { - "_id": "America/Kralendijk", - "name": "America/Kralendijk" - }, - { - "_id": "America/La_Paz", - "name": "America/La_Paz" - }, - { - "_id": "America/Lima", - "name": "America/Lima" - }, - { - "_id": "America/Los_Angeles", - "name": "America/Los_Angeles" - }, - { - "_id": "America/Lower_Princes", - "name": "America/Lower_Princes" - }, - { - "_id": "America/Maceio", - "name": "America/Maceio" - }, - { - "_id": "America/Managua", - "name": "America/Managua" - }, - { - "_id": "America/Manaus", - "name": "America/Manaus" - }, - { - "_id": "America/Marigot", - "name": "America/Marigot" - }, - { - "_id": "America/Martinique", - "name": "America/Martinique" - }, - { - "_id": "America/Matamoros", - "name": "America/Matamoros" - }, - { - "_id": "America/Mazatlan", - "name": "America/Mazatlan" - }, - { - "_id": "America/Menominee", - "name": "America/Menominee" - }, - { - "_id": "America/Merida", - "name": "America/Merida" - }, - { - "_id": "America/Metlakatla", - "name": "America/Metlakatla" - }, - { - "_id": "America/Mexico_City", - "name": "America/Mexico_City" - }, - { - "_id": "America/Miquelon", - "name": "America/Miquelon" - }, - { - "_id": "America/Moncton", - "name": "America/Moncton" - }, - { - "_id": "America/Monterrey", - "name": "America/Monterrey" - }, - { - "_id": "America/Montevideo", - "name": "America/Montevideo" - }, - { - "_id": "America/Montserrat", - "name": "America/Montserrat" - }, - { - "_id": "America/Nassau", - "name": "America/Nassau" - }, - { - "_id": "America/New_York", - "name": "America/New_York" - }, - { - "_id": "America/Nipigon", - "name": "America/Nipigon" - }, - { - "_id": "America/Nome", - "name": "America/Nome" - }, - { - "_id": "America/Noronha", - "name": "America/Noronha" - }, - { - "_id": "America/North_Dakota/Beulah", - "name": "America/North_Dakota/Beulah" - }, - { - "_id": "America/North_Dakota/Center", - "name": "America/North_Dakota/Center" - }, - { - "_id": "America/North_Dakota/New_Salem", - "name": "America/North_Dakota/New_Salem" - }, - { - "_id": "America/Ojinaga", - "name": "America/Ojinaga" - }, - { - "_id": "America/Panama", - "name": "America/Panama" - }, - { - "_id": "America/Pangnirtung", - "name": "America/Pangnirtung" - }, - { - "_id": "America/Paramaribo", - "name": "America/Paramaribo" - }, - { - "_id": "America/Phoenix", - "name": "America/Phoenix" - }, - { - "_id": "America/Port_of_Spain", - "name": "America/Port_of_Spain" - }, - { - "_id": "America/Port-au-Prince", - "name": "America/Port-au-Prince" - }, - { - "_id": "America/Porto_Velho", - "name": "America/Porto_Velho" - }, - { - "_id": "America/Puerto_Rico", - "name": "America/Puerto_Rico" - }, - { - "_id": "America/Punta_Arenas", - "name": "America/Punta_Arenas" - }, - { - "_id": "America/Rainy_River", - "name": "America/Rainy_River" - }, - { - "_id": "America/Rankin_Inlet", - "name": "America/Rankin_Inlet" - }, - { - "_id": "America/Recife", - "name": "America/Recife" - }, - { - "_id": "America/Regina", - "name": "America/Regina" - }, - { - "_id": "America/Resolute", - "name": "America/Resolute" - }, - { - "_id": "America/Rio_Branco", - "name": "America/Rio_Branco" - }, - { - "_id": "America/Santarem", - "name": "America/Santarem" - }, - { - "_id": "America/Santiago", - "name": "America/Santiago" - }, - { - "_id": "America/Santo_Domingo", - "name": "America/Santo_Domingo" - }, - { - "_id": "America/Sao_Paulo", - "name": "America/Sao_Paulo" - }, - { - "_id": "America/Scoresbysund", - "name": "America/Scoresbysund" - }, - { - "_id": "America/Sitka", - "name": "America/Sitka" - }, - { - "_id": "America/St_Barthelemy", - "name": "America/St_Barthelemy" - }, - { - "_id": "America/St_Johns", - "name": "America/St_Johns" - }, - { - "_id": "America/St_Kitts", - "name": "America/St_Kitts" - }, - { - "_id": "America/St_Lucia", - "name": "America/St_Lucia" - }, - { - "_id": "America/St_Thomas", - "name": "America/St_Thomas" - }, - { - "_id": "America/St_Vincent", - "name": "America/St_Vincent" - }, - { - "_id": "America/Swift_Current", - "name": "America/Swift_Current" - }, - { - "_id": "America/Tegucigalpa", - "name": "America/Tegucigalpa" - }, - { - "_id": "America/Thule", - "name": "America/Thule" - }, - { - "_id": "America/Thunder_Bay", - "name": "America/Thunder_Bay" - }, - { - "_id": "America/Tijuana", - "name": "America/Tijuana" - }, - { - "_id": "America/Toronto", - "name": "America/Toronto" - }, - { - "_id": "America/Tortola", - "name": "America/Tortola" - }, - { - "_id": "America/Vancouver", - "name": "America/Vancouver" - }, - { - "_id": "America/Whitehorse", - "name": "America/Whitehorse" - }, - { - "_id": "America/Winnipeg", - "name": "America/Winnipeg" - }, - { - "_id": "America/Yakutat", - "name": "America/Yakutat" - }, - { - "_id": "America/Yellowknife", - "name": "America/Yellowknife" - }, - { - "_id": "Antarctica/Casey", - "name": "Antarctica/Casey" - }, - { - "_id": "Antarctica/Davis", - "name": "Antarctica/Davis" - }, - { - "_id": "Antarctica/DumontDUrville", - "name": "Antarctica/DumontDUrville" - }, - { - "_id": "Antarctica/Macquarie", - "name": "Antarctica/Macquarie" - }, - { - "_id": "Antarctica/Mawson", - "name": "Antarctica/Mawson" - }, - { - "_id": "Antarctica/McMurdo", - "name": "Antarctica/McMurdo" - }, - { - "_id": "Antarctica/Palmer", - "name": "Antarctica/Palmer" - }, - { - "_id": "Antarctica/Rothera", - "name": "Antarctica/Rothera" - }, - { - "_id": "Antarctica/Syowa", - "name": "Antarctica/Syowa" - }, - { - "_id": "Antarctica/Troll", - "name": "Antarctica/Troll" - }, - { - "_id": "Antarctica/Vostok", - "name": "Antarctica/Vostok" - }, - { - "_id": "Arctic/Longyearbyen", - "name": "Arctic/Longyearbyen" - }, - { - "_id": "Asia/Aden", - "name": "Asia/Aden" - }, - { - "_id": "Asia/Almaty", - "name": "Asia/Almaty" - }, - { - "_id": "Asia/Amman", - "name": "Asia/Amman" - }, - { - "_id": "Asia/Anadyr", - "name": "Asia/Anadyr" - }, - { - "_id": "Asia/Aqtau", - "name": "Asia/Aqtau" - }, - { - "_id": "Asia/Aqtobe", - "name": "Asia/Aqtobe" - }, - { - "_id": "Asia/Ashgabat", - "name": "Asia/Ashgabat" - }, - { - "_id": "Asia/Atyrau", - "name": "Asia/Atyrau" - }, - { - "_id": "Asia/Baghdad", - "name": "Asia/Baghdad" - }, - { - "_id": "Asia/Bahrain", - "name": "Asia/Bahrain" - }, - { - "_id": "Asia/Baku", - "name": "Asia/Baku" - }, - { - "_id": "Asia/Bangkok", - "name": "Asia/Bangkok" - }, - { - "_id": "Asia/Barnaul", - "name": "Asia/Barnaul" - }, - { - "_id": "Asia/Beirut", - "name": "Asia/Beirut" - }, - { - "_id": "Asia/Bishkek", - "name": "Asia/Bishkek" - }, - { - "_id": "Asia/Brunei", - "name": "Asia/Brunei" - }, - { - "_id": "Asia/Chita", - "name": "Asia/Chita" - }, - { - "_id": "Asia/Choibalsan", - "name": "Asia/Choibalsan" - }, - { - "_id": "Asia/Colombo", - "name": "Asia/Colombo" - }, - { - "_id": "Asia/Damascus", - "name": "Asia/Damascus" - }, - { - "_id": "Asia/Dhaka", - "name": "Asia/Dhaka" - }, - { - "_id": "Asia/Dili", - "name": "Asia/Dili" - }, - { - "_id": "Asia/Dubai", - "name": "Asia/Dubai" - }, - { - "_id": "Asia/Dushanbe", - "name": "Asia/Dushanbe" - }, - { - "_id": "Asia/Famagusta", - "name": "Asia/Famagusta" - }, - { - "_id": "Asia/Gaza", - "name": "Asia/Gaza" - }, - { - "_id": "Asia/Hebron", - "name": "Asia/Hebron" - }, - { - "_id": "Asia/Ho_Chi_Minh", - "name": "Asia/Ho_Chi_Minh" - }, - { - "_id": "Asia/Hong_Kong", - "name": "Asia/Hong_Kong" - }, - { - "_id": "Asia/Hovd", - "name": "Asia/Hovd" - }, - { - "_id": "Asia/Irkutsk", - "name": "Asia/Irkutsk" - }, - { - "_id": "Asia/Jakarta", - "name": "Asia/Jakarta" - }, - { - "_id": "Asia/Jayapura", - "name": "Asia/Jayapura" - }, - { - "_id": "Asia/Jerusalem", - "name": "Asia/Jerusalem" - }, - { - "_id": "Asia/Kabul", - "name": "Asia/Kabul" - }, - { - "_id": "Asia/Kamchatka", - "name": "Asia/Kamchatka" - }, - { - "_id": "Asia/Karachi", - "name": "Asia/Karachi" - }, - { - "_id": "Asia/Kathmandu", - "name": "Asia/Kathmandu" - }, - { - "_id": "Asia/Khandyga", - "name": "Asia/Khandyga" - }, - { - "_id": "Asia/Kolkata", - "name": "Asia/Kolkata" - }, - { - "_id": "Asia/Krasnoyarsk", - "name": "Asia/Krasnoyarsk" - }, - { - "_id": "Asia/Kuala_Lumpur", - "name": "Asia/Kuala_Lumpur" - }, - { - "_id": "Asia/Kuching", - "name": "Asia/Kuching" - }, - { - "_id": "Asia/Kuwait", - "name": "Asia/Kuwait" - }, - { - "_id": "Asia/Macau", - "name": "Asia/Macau" - }, - { - "_id": "Asia/Magadan", - "name": "Asia/Magadan" - }, - { - "_id": "Asia/Makassar", - "name": "Asia/Makassar" - }, - { - "_id": "Asia/Manila", - "name": "Asia/Manila" - }, - { - "_id": "Asia/Muscat", - "name": "Asia/Muscat" - }, - { - "_id": "Asia/Nicosia", - "name": "Asia/Nicosia" - }, - { - "_id": "Asia/Novokuznetsk", - "name": "Asia/Novokuznetsk" - }, - { - "_id": "Asia/Novosibirsk", - "name": "Asia/Novosibirsk" - }, - { - "_id": "Asia/Omsk", - "name": "Asia/Omsk" - }, - { - "_id": "Asia/Oral", - "name": "Asia/Oral" - }, - { - "_id": "Asia/Phnom_Penh", - "name": "Asia/Phnom_Penh" - }, - { - "_id": "Asia/Pontianak", - "name": "Asia/Pontianak" - }, - { - "_id": "Asia/Pyongyang", - "name": "Asia/Pyongyang" - }, - { - "_id": "Asia/Qatar", - "name": "Asia/Qatar" - }, - { - "_id": "Asia/Qostanay", - "name": "Asia/Qostanay" - }, - { - "_id": "Asia/Qyzylorda", - "name": "Asia/Qyzylorda" - }, - { - "_id": "Asia/Riyadh", - "name": "Asia/Riyadh" - }, - { - "_id": "Asia/Sakhalin", - "name": "Asia/Sakhalin" - }, - { - "_id": "Asia/Samarkand", - "name": "Asia/Samarkand" - }, - { - "_id": "Asia/Seoul", - "name": "Asia/Seoul" - }, - { - "_id": "Asia/Shanghai", - "name": "Asia/Shanghai" - }, - { - "_id": "Asia/Singapore", - "name": "Asia/Singapore" - }, - { - "_id": "Asia/Srednekolymsk", - "name": "Asia/Srednekolymsk" - }, - { - "_id": "Asia/Taipei", - "name": "Asia/Taipei" - }, - { - "_id": "Asia/Tashkent", - "name": "Asia/Tashkent" - }, - { - "_id": "Asia/Tbilisi", - "name": "Asia/Tbilisi" - }, - { - "_id": "Asia/Tehran", - "name": "Asia/Tehran" - }, - { - "_id": "Asia/Thimphu", - "name": "Asia/Thimphu" - }, - { - "_id": "Asia/Tokyo", - "name": "Asia/Tokyo" - }, - { - "_id": "Asia/Tomsk", - "name": "Asia/Tomsk" - }, - { - "_id": "Asia/Ulaanbaatar", - "name": "Asia/Ulaanbaatar" - }, - { - "_id": "Asia/Urumqi", - "name": "Asia/Urumqi" - }, - { - "_id": "Asia/Ust-Nera", - "name": "Asia/Ust-Nera" - }, - { - "_id": "Asia/Vientiane", - "name": "Asia/Vientiane" - }, - { - "_id": "Asia/Vladivostok", - "name": "Asia/Vladivostok" - }, - { - "_id": "Asia/Yakutsk", - "name": "Asia/Yakutsk" - }, - { - "_id": "Asia/Yangon", - "name": "Asia/Yangon" - }, - { - "_id": "Asia/Yekaterinburg", - "name": "Asia/Yekaterinburg" - }, - { - "_id": "Asia/Yerevan", - "name": "Asia/Yerevan" - }, - { - "_id": "Atlantic/Azores", - "name": "Atlantic/Azores" - }, - { - "_id": "Atlantic/Bermuda", - "name": "Atlantic/Bermuda" - }, - { - "_id": "Atlantic/Canary", - "name": "Atlantic/Canary" - }, - { - "_id": "Atlantic/Cape_Verde", - "name": "Atlantic/Cape_Verde" - }, - { - "_id": "Atlantic/Faroe", - "name": "Atlantic/Faroe" - }, - { - "_id": "Atlantic/Madeira", - "name": "Atlantic/Madeira" - }, - { - "_id": "Atlantic/Reykjavik", - "name": "Atlantic/Reykjavik" - }, - { - "_id": "Atlantic/South_Georgia", - "name": "Atlantic/South_Georgia" - }, - { - "_id": "Atlantic/St_Helena", - "name": "Atlantic/St_Helena" - }, - { - "_id": "Atlantic/Stanley", - "name": "Atlantic/Stanley" - }, - { - "_id": "Australia/Adelaide", - "name": "Australia/Adelaide" - }, - { - "_id": "Australia/Brisbane", - "name": "Australia/Brisbane" - }, - { - "_id": "Australia/Broken_Hill", - "name": "Australia/Broken_Hill" - }, - { - "_id": "Australia/Currie", - "name": "Australia/Currie" - }, - { - "_id": "Australia/Darwin", - "name": "Australia/Darwin" - }, - { - "_id": "Australia/Eucla", - "name": "Australia/Eucla" - }, - { - "_id": "Australia/Hobart", - "name": "Australia/Hobart" - }, - { - "_id": "Australia/Lindeman", - "name": "Australia/Lindeman" - }, - { - "_id": "Australia/Lord_Howe", - "name": "Australia/Lord_Howe" - }, - { - "_id": "Australia/Melbourne", - "name": "Australia/Melbourne" - }, - { - "_id": "Australia/Perth", - "name": "Australia/Perth" - }, - { - "_id": "Australia/Sydney", - "name": "Australia/Sydney" - }, - { - "_id": "Europe/Amsterdam", - "name": "Europe/Amsterdam" - }, - { - "_id": "Europe/Andorra", - "name": "Europe/Andorra" - }, - { - "_id": "Europe/Astrakhan", - "name": "Europe/Astrakhan" - }, - { - "_id": "Europe/Athens", - "name": "Europe/Athens" - }, - { - "_id": "Europe/Belgrade", - "name": "Europe/Belgrade" - }, - { - "_id": "Europe/Berlin", - "name": "Europe/Berlin" - }, - { - "_id": "Europe/Bratislava", - "name": "Europe/Bratislava" - }, - { - "_id": "Europe/Brussels", - "name": "Europe/Brussels" - }, - { - "_id": "Europe/Bucharest", - "name": "Europe/Bucharest" - }, - { - "_id": "Europe/Budapest", - "name": "Europe/Budapest" - }, - { - "_id": "Europe/Chisinau", - "name": "Europe/Chisinau" - }, - { - "_id": "Europe/Copenhagen", - "name": "Europe/Copenhagen" - }, - { - "_id": "Europe/Dublin", - "name": "Europe/Dublin" - }, - { - "_id": "Europe/Gibraltar", - "name": "Europe/Gibraltar" - }, - { - "_id": "Europe/Guernsey", - "name": "Europe/Guernsey" - }, - { - "_id": "Europe/Helsinki", - "name": "Europe/Helsinki" - }, - { - "_id": "Europe/Isle_of_Man", - "name": "Europe/Isle_of_Man" - }, - { - "_id": "Europe/Istanbul", - "name": "Europe/Istanbul" - }, - { - "_id": "Europe/Jersey", - "name": "Europe/Jersey" - }, - { - "_id": "Europe/Kaliningrad", - "name": "Europe/Kaliningrad" - }, - { - "_id": "Europe/Kirov", - "name": "Europe/Kirov" - }, - { - "_id": "Europe/Kyiv", - "name": "Europe/Kyiv" - }, - { - "_id": "Europe/Lisbon", - "name": "Europe/Lisbon" - }, - { - "_id": "Europe/Ljubljana", - "name": "Europe/Ljubljana" - }, - { - "_id": "Europe/London", - "name": "Europe/London" - }, - { - "_id": "Europe/Luxembourg", - "name": "Europe/Luxembourg" - }, - { - "_id": "Europe/Madrid", - "name": "Europe/Madrid" - }, - { - "_id": "Europe/Malta", - "name": "Europe/Malta" - }, - { - "_id": "Europe/Mariehamn", - "name": "Europe/Mariehamn" - }, - { - "_id": "Europe/Minsk", - "name": "Europe/Minsk" - }, - { - "_id": "Europe/Monaco", - "name": "Europe/Monaco" - }, - { - "_id": "Europe/Moscow", - "name": "Europe/Moscow" - }, - { - "_id": "Europe/Oslo", - "name": "Europe/Oslo" - }, - { - "_id": "Europe/Paris", - "name": "Europe/Paris" - }, - { - "_id": "Europe/Podgorica", - "name": "Europe/Podgorica" - }, - { - "_id": "Europe/Prague", - "name": "Europe/Prague" - }, - { - "_id": "Europe/Riga", - "name": "Europe/Riga" - }, - { - "_id": "Europe/Rome", - "name": "Europe/Rome" - }, - { - "_id": "Europe/Samara", - "name": "Europe/Samara" - }, - { - "_id": "Europe/San_Marino", - "name": "Europe/San_Marino" - }, - { - "_id": "Europe/Sarajevo", - "name": "Europe/Sarajevo" - }, - { - "_id": "Europe/Saratov", - "name": "Europe/Saratov" - }, - { - "_id": "Europe/Simferopol", - "name": "Europe/Simferopol" - }, - { - "_id": "Europe/Skopje", - "name": "Europe/Skopje" - }, - { - "_id": "Europe/Sofia", - "name": "Europe/Sofia" - }, - { - "_id": "Europe/Stockholm", - "name": "Europe/Stockholm" - }, - { - "_id": "Europe/Tallinn", - "name": "Europe/Tallinn" - }, - { - "_id": "Europe/Tirane", - "name": "Europe/Tirane" - }, - { - "_id": "Europe/Ulyanovsk", - "name": "Europe/Ulyanovsk" - }, - { - "_id": "Europe/Uzhgorod", - "name": "Europe/Uzhgorod" - }, - { - "_id": "Europe/Vaduz", - "name": "Europe/Vaduz" - }, - { - "_id": "Europe/Vatican", - "name": "Europe/Vatican" - }, - { - "_id": "Europe/Vienna", - "name": "Europe/Vienna" - }, - { - "_id": "Europe/Vilnius", - "name": "Europe/Vilnius" - }, - { - "_id": "Europe/Volgograd", - "name": "Europe/Volgograd" - }, - { - "_id": "Europe/Warsaw", - "name": "Europe/Warsaw" - }, - { - "_id": "Europe/Zagreb", - "name": "Europe/Zagreb" - }, - { - "_id": "Europe/Zaporizhzhia", - "name": "Europe/Zaporizhzhia" - }, - { - "_id": "Europe/Zurich", - "name": "Europe/Zurich" - }, - { - "_id": "Indian/Antananarivo", - "name": "Indian/Antananarivo" - }, - { - "_id": "Indian/Chagos", - "name": "Indian/Chagos" - }, - { - "_id": "Indian/Christmas", - "name": "Indian/Christmas" - }, - { - "_id": "Indian/Cocos", - "name": "Indian/Cocos" - }, - { - "_id": "Indian/Comoro", - "name": "Indian/Comoro" - }, - { - "_id": "Indian/Kerguelen", - "name": "Indian/Kerguelen" - }, - { - "_id": "Indian/Mahe", - "name": "Indian/Mahe" - }, - { - "_id": "Indian/Maldives", - "name": "Indian/Maldives" - }, - { - "_id": "Indian/Mauritius", - "name": "Indian/Mauritius" - }, - { - "_id": "Indian/Mayotte", - "name": "Indian/Mayotte" - }, - { - "_id": "Indian/Reunion", - "name": "Indian/Reunion" - }, - { - "_id": "Pacific/Apia", - "name": "Pacific/Apia" - }, - { - "_id": "Pacific/Auckland", - "name": "Pacific/Auckland" - }, - { - "_id": "Pacific/Bougainville", - "name": "Pacific/Bougainville" - }, - { - "_id": "Pacific/Chatham", - "name": "Pacific/Chatham" - }, - { - "_id": "Pacific/Chuuk", - "name": "Pacific/Chuuk" - }, - { - "_id": "Pacific/Easter", - "name": "Pacific/Easter" - }, - { - "_id": "Pacific/Efate", - "name": "Pacific/Efate" - }, - { - "_id": "Pacific/Enderbury", - "name": "Pacific/Enderbury" - }, - { - "_id": "Pacific/Fakaofo", - "name": "Pacific/Fakaofo" - }, - { - "_id": "Pacific/Fiji", - "name": "Pacific/Fiji" - }, - { - "_id": "Pacific/Funafuti", - "name": "Pacific/Funafuti" - }, - { - "_id": "Pacific/Galapagos", - "name": "Pacific/Galapagos" - }, - { - "_id": "Pacific/Gambier", - "name": "Pacific/Gambier" - }, - { - "_id": "Pacific/Guadalcanal", - "name": "Pacific/Guadalcanal" - }, - { - "_id": "Pacific/Guam", - "name": "Pacific/Guam" - }, - { - "_id": "Pacific/Honolulu", - "name": "Pacific/Honolulu" - }, - { - "_id": "Pacific/Kiritimati", - "name": "Pacific/Kiritimati" - }, - { - "_id": "Pacific/Kosrae", - "name": "Pacific/Kosrae" - }, - { - "_id": "Pacific/Kwajalein", - "name": "Pacific/Kwajalein" - }, - { - "_id": "Pacific/Majuro", - "name": "Pacific/Majuro" - }, - { - "_id": "Pacific/Marquesas", - "name": "Pacific/Marquesas" - }, - { - "_id": "Pacific/Midway", - "name": "Pacific/Midway" - }, - { - "_id": "Pacific/Nauru", - "name": "Pacific/Nauru" - }, - { - "_id": "Pacific/Niue", - "name": "Pacific/Niue" - }, - { - "_id": "Pacific/Norfolk", - "name": "Pacific/Norfolk" - }, - { - "_id": "Pacific/Noumea", - "name": "Pacific/Noumea" - }, - { - "_id": "Pacific/Pago_Pago", - "name": "Pacific/Pago_Pago" - }, - { - "_id": "Pacific/Palau", - "name": "Pacific/Palau" - }, - { - "_id": "Pacific/Pitcairn", - "name": "Pacific/Pitcairn" - }, - { - "_id": "Pacific/Pohnpei", - "name": "Pacific/Pohnpei" - }, - { - "_id": "Pacific/Port_Moresby", - "name": "Pacific/Port_Moresby" - }, - { - "_id": "Pacific/Rarotonga", - "name": "Pacific/Rarotonga" - }, - { - "_id": "Pacific/Saipan", - "name": "Pacific/Saipan" - }, - { - "_id": "Pacific/Tahiti", - "name": "Pacific/Tahiti" - }, - { - "_id": "Pacific/Tarawa", - "name": "Pacific/Tarawa" - }, - { - "_id": "Pacific/Tongatapu", - "name": "Pacific/Tongatapu" - }, - { - "_id": "Pacific/Wake", - "name": "Pacific/Wake" - }, - { - "_id": "Pacific/Wallis", - "name": "Pacific/Wallis" - } + { + "_id": "Africa/Abidjan", + "name": "Africa/Abidjan" + }, + { + "_id": "Africa/Accra", + "name": "Africa/Accra" + }, + { + "_id": "Africa/Addis_Ababa", + "name": "Africa/Addis_Ababa" + }, + { + "_id": "Africa/Algiers", + "name": "Africa/Algiers" + }, + { + "_id": "Africa/Asmara", + "name": "Africa/Asmara" + }, + { + "_id": "Africa/Bamako", + "name": "Africa/Bamako" + }, + { + "_id": "Africa/Bangui", + "name": "Africa/Bangui" + }, + { + "_id": "Africa/Banjul", + "name": "Africa/Banjul" + }, + { + "_id": "Africa/Bissau", + "name": "Africa/Bissau" + }, + { + "_id": "Africa/Blantyre", + "name": "Africa/Blantyre" + }, + { + "_id": "Africa/Brazzaville", + "name": "Africa/Brazzaville" + }, + { + "_id": "Africa/Bujumbura", + "name": "Africa/Bujumbura" + }, + { + "_id": "Africa/Cairo", + "name": "Africa/Cairo" + }, + { + "_id": "Africa/Casablanca", + "name": "Africa/Casablanca" + }, + { + "_id": "Africa/Ceuta", + "name": "Africa/Ceuta" + }, + { + "_id": "Africa/Conakry", + "name": "Africa/Conakry" + }, + { + "_id": "Africa/Dakar", + "name": "Africa/Dakar" + }, + { + "_id": "Africa/Dar_es_Salaam", + "name": "Africa/Dar_es_Salaam" + }, + { + "_id": "Africa/Djibouti", + "name": "Africa/Djibouti" + }, + { + "_id": "Africa/Douala", + "name": "Africa/Douala" + }, + { + "_id": "Africa/El_Aaiun", + "name": "Africa/El_Aaiun" + }, + { + "_id": "Africa/Freetown", + "name": "Africa/Freetown" + }, + { + "_id": "Africa/Gaborone", + "name": "Africa/Gaborone" + }, + { + "_id": "Africa/Harare", + "name": "Africa/Harare" + }, + { + "_id": "Africa/Johannesburg", + "name": "Africa/Johannesburg" + }, + { + "_id": "Africa/Juba", + "name": "Africa/Juba" + }, + { + "_id": "Africa/Kampala", + "name": "Africa/Kampala" + }, + { + "_id": "Africa/Khartoum", + "name": "Africa/Khartoum" + }, + { + "_id": "Africa/Kigali", + "name": "Africa/Kigali" + }, + { + "_id": "Africa/Kinshasa", + "name": "Africa/Kinshasa" + }, + { + "_id": "Africa/Lagos", + "name": "Africa/Lagos" + }, + { + "_id": "Africa/Libreville", + "name": "Africa/Libreville" + }, + { + "_id": "Africa/Lome", + "name": "Africa/Lome" + }, + { + "_id": "Africa/Luanda", + "name": "Africa/Luanda" + }, + { + "_id": "Africa/Lubumbashi", + "name": "Africa/Lubumbashi" + }, + { + "_id": "Africa/Lusaka", + "name": "Africa/Lusaka" + }, + { + "_id": "Africa/Malabo", + "name": "Africa/Malabo" + }, + { + "_id": "Africa/Maputo", + "name": "Africa/Maputo" + }, + { + "_id": "Africa/Maseru", + "name": "Africa/Maseru" + }, + { + "_id": "Africa/Mbabane", + "name": "Africa/Mbabane" + }, + { + "_id": "Africa/Mogadishu", + "name": "Africa/Mogadishu" + }, + { + "_id": "Africa/Monrovia", + "name": "Africa/Monrovia" + }, + { + "_id": "Africa/Nairobi", + "name": "Africa/Nairobi" + }, + { + "_id": "Africa/Ndjamena", + "name": "Africa/Ndjamena" + }, + { + "_id": "Africa/Niamey", + "name": "Africa/Niamey" + }, + { + "_id": "Africa/Nouakchott", + "name": "Africa/Nouakchott" + }, + { + "_id": "Africa/Ouagadougou", + "name": "Africa/Ouagadougou" + }, + { + "_id": "Africa/Porto-Novo", + "name": "Africa/Porto-Novo" + }, + { + "_id": "Africa/Sao_Tome", + "name": "Africa/Sao_Tome" + }, + { + "_id": "Africa/Tripoli", + "name": "Africa/Tripoli" + }, + { + "_id": "Africa/Tunis", + "name": "Africa/Tunis" + }, + { + "_id": "Africa/Windhoek", + "name": "Africa/Windhoek" + }, + { + "_id": "America/Adak", + "name": "America/Adak" + }, + { + "_id": "America/Anchorage", + "name": "America/Anchorage" + }, + { + "_id": "America/Anguilla", + "name": "America/Anguilla" + }, + { + "_id": "America/Antigua", + "name": "America/Antigua" + }, + { + "_id": "America/Araguaina", + "name": "America/Araguaina" + }, + { + "_id": "America/Argentina/Buenos_Aires", + "name": "America/Argentina/Buenos_Aires" + }, + { + "_id": "America/Argentina/Catamarca", + "name": "America/Argentina/Catamarca" + }, + { + "_id": "America/Argentina/Cordoba", + "name": "America/Argentina/Cordoba" + }, + { + "_id": "America/Argentina/Jujuy", + "name": "America/Argentina/Jujuy" + }, + { + "_id": "America/Argentina/La_Rioja", + "name": "America/Argentina/La_Rioja" + }, + { + "_id": "America/Argentina/Mendoza", + "name": "America/Argentina/Mendoza" + }, + { + "_id": "America/Argentina/Rio_Gallegos", + "name": "America/Argentina/Rio_Gallegos" + }, + { + "_id": "America/Argentina/Salta", + "name": "America/Argentina/Salta" + }, + { + "_id": "America/Argentina/San_Juan", + "name": "America/Argentina/San_Juan" + }, + { + "_id": "America/Argentina/San_Luis", + "name": "America/Argentina/San_Luis" + }, + { + "_id": "America/Argentina/Tucuman", + "name": "America/Argentina/Tucuman" + }, + { + "_id": "America/Argentina/Ushuaia", + "name": "America/Argentina/Ushuaia" + }, + { + "_id": "America/Aruba", + "name": "America/Aruba" + }, + { + "_id": "America/Asuncion", + "name": "America/Asuncion" + }, + { + "_id": "America/Atikokan", + "name": "America/Atikokan" + }, + { + "_id": "America/Bahia", + "name": "America/Bahia" + }, + { + "_id": "America/Bahia_Banderas", + "name": "America/Bahia_Banderas" + }, + { + "_id": "America/Barbados", + "name": "America/Barbados" + }, + { + "_id": "America/Belem", + "name": "America/Belem" + }, + { + "_id": "America/Belize", + "name": "America/Belize" + }, + { + "_id": "America/Blanc-Sablon", + "name": "America/Blanc-Sablon" + }, + { + "_id": "America/Boa_Vista", + "name": "America/Boa_Vista" + }, + { + "_id": "America/Bogota", + "name": "America/Bogota" + }, + { + "_id": "America/Boise", + "name": "America/Boise" + }, + { + "_id": "America/Cambridge_Bay", + "name": "America/Cambridge_Bay" + }, + { + "_id": "America/Campo_Grande", + "name": "America/Campo_Grande" + }, + { + "_id": "America/Cancun", + "name": "America/Cancun" + }, + { + "_id": "America/Caracas", + "name": "America/Caracas" + }, + { + "_id": "America/Cayenne", + "name": "America/Cayenne" + }, + { + "_id": "America/Cayman", + "name": "America/Cayman" + }, + { + "_id": "America/Chicago", + "name": "America/Chicago" + }, + { + "_id": "America/Chihuahua", + "name": "America/Chihuahua" + }, + { + "_id": "America/Costa_Rica", + "name": "America/Costa_Rica" + }, + { + "_id": "America/Creston", + "name": "America/Creston" + }, + { + "_id": "America/Cuiaba", + "name": "America/Cuiaba" + }, + { + "_id": "America/Curacao", + "name": "America/Curacao" + }, + { + "_id": "America/Danmarkshavn", + "name": "America/Danmarkshavn" + }, + { + "_id": "America/Dawson", + "name": "America/Dawson" + }, + { + "_id": "America/Dawson_Creek", + "name": "America/Dawson_Creek" + }, + { + "_id": "America/Denver", + "name": "America/Denver" + }, + { + "_id": "America/Detroit", + "name": "America/Detroit" + }, + { + "_id": "America/Dominica", + "name": "America/Dominica" + }, + { + "_id": "America/Edmonton", + "name": "America/Edmonton" + }, + { + "_id": "America/Eirunepe", + "name": "America/Eirunepe" + }, + { + "_id": "America/El_Salvador", + "name": "America/El_Salvador" + }, + { + "_id": "America/Fort_Nelson", + "name": "America/Fort_Nelson" + }, + { + "_id": "America/Fortaleza", + "name": "America/Fortaleza" + }, + { + "_id": "America/Glace_Bay", + "name": "America/Glace_Bay" + }, + { + "_id": "America/Godthab", + "name": "America/Godthab" + }, + { + "_id": "America/Goose_Bay", + "name": "America/Goose_Bay" + }, + { + "_id": "America/Grand_Turk", + "name": "America/Grand_Turk" + }, + { + "_id": "America/Grenada", + "name": "America/Grenada" + }, + { + "_id": "America/Guadeloupe", + "name": "America/Guadeloupe" + }, + { + "_id": "America/Guatemala", + "name": "America/Guatemala" + }, + { + "_id": "America/Guayaquil", + "name": "America/Guayaquil" + }, + { + "_id": "America/Guyana", + "name": "America/Guyana" + }, + { + "_id": "America/Halifax", + "name": "America/Halifax" + }, + { + "_id": "America/Havana", + "name": "America/Havana" + }, + { + "_id": "America/Hermosillo", + "name": "America/Hermosillo" + }, + { + "_id": "America/Indiana/Indianapolis", + "name": "America/Indiana/Indianapolis" + }, + { + "_id": "America/Indiana/Knox", + "name": "America/Indiana/Knox" + }, + { + "_id": "America/Indiana/Marengo", + "name": "America/Indiana/Marengo" + }, + { + "_id": "America/Indiana/Petersburg", + "name": "America/Indiana/Petersburg" + }, + { + "_id": "America/Indiana/Tell_City", + "name": "America/Indiana/Tell_City" + }, + { + "_id": "America/Indiana/Vevay", + "name": "America/Indiana/Vevay" + }, + { + "_id": "America/Indiana/Vincennes", + "name": "America/Indiana/Vincennes" + }, + { + "_id": "America/Indiana/Winamac", + "name": "America/Indiana/Winamac" + }, + { + "_id": "America/Inuvik", + "name": "America/Inuvik" + }, + { + "_id": "America/Iqaluit", + "name": "America/Iqaluit" + }, + { + "_id": "America/Jamaica", + "name": "America/Jamaica" + }, + { + "_id": "America/Juneau", + "name": "America/Juneau" + }, + { + "_id": "America/Kentucky/Louisville", + "name": "America/Kentucky/Louisville" + }, + { + "_id": "America/Kentucky/Monticello", + "name": "America/Kentucky/Monticello" + }, + { + "_id": "America/Kralendijk", + "name": "America/Kralendijk" + }, + { + "_id": "America/La_Paz", + "name": "America/La_Paz" + }, + { + "_id": "America/Lima", + "name": "America/Lima" + }, + { + "_id": "America/Los_Angeles", + "name": "America/Los_Angeles" + }, + { + "_id": "America/Lower_Princes", + "name": "America/Lower_Princes" + }, + { + "_id": "America/Maceio", + "name": "America/Maceio" + }, + { + "_id": "America/Managua", + "name": "America/Managua" + }, + { + "_id": "America/Manaus", + "name": "America/Manaus" + }, + { + "_id": "America/Marigot", + "name": "America/Marigot" + }, + { + "_id": "America/Martinique", + "name": "America/Martinique" + }, + { + "_id": "America/Matamoros", + "name": "America/Matamoros" + }, + { + "_id": "America/Mazatlan", + "name": "America/Mazatlan" + }, + { + "_id": "America/Menominee", + "name": "America/Menominee" + }, + { + "_id": "America/Merida", + "name": "America/Merida" + }, + { + "_id": "America/Metlakatla", + "name": "America/Metlakatla" + }, + { + "_id": "America/Mexico_City", + "name": "America/Mexico_City" + }, + { + "_id": "America/Miquelon", + "name": "America/Miquelon" + }, + { + "_id": "America/Moncton", + "name": "America/Moncton" + }, + { + "_id": "America/Monterrey", + "name": "America/Monterrey" + }, + { + "_id": "America/Montevideo", + "name": "America/Montevideo" + }, + { + "_id": "America/Montserrat", + "name": "America/Montserrat" + }, + { + "_id": "America/Nassau", + "name": "America/Nassau" + }, + { + "_id": "America/New_York", + "name": "America/New_York" + }, + { + "_id": "America/Nipigon", + "name": "America/Nipigon" + }, + { + "_id": "America/Nome", + "name": "America/Nome" + }, + { + "_id": "America/Noronha", + "name": "America/Noronha" + }, + { + "_id": "America/North_Dakota/Beulah", + "name": "America/North_Dakota/Beulah" + }, + { + "_id": "America/North_Dakota/Center", + "name": "America/North_Dakota/Center" + }, + { + "_id": "America/North_Dakota/New_Salem", + "name": "America/North_Dakota/New_Salem" + }, + { + "_id": "America/Ojinaga", + "name": "America/Ojinaga" + }, + { + "_id": "America/Panama", + "name": "America/Panama" + }, + { + "_id": "America/Pangnirtung", + "name": "America/Pangnirtung" + }, + { + "_id": "America/Paramaribo", + "name": "America/Paramaribo" + }, + { + "_id": "America/Phoenix", + "name": "America/Phoenix" + }, + { + "_id": "America/Port_of_Spain", + "name": "America/Port_of_Spain" + }, + { + "_id": "America/Port-au-Prince", + "name": "America/Port-au-Prince" + }, + { + "_id": "America/Porto_Velho", + "name": "America/Porto_Velho" + }, + { + "_id": "America/Puerto_Rico", + "name": "America/Puerto_Rico" + }, + { + "_id": "America/Punta_Arenas", + "name": "America/Punta_Arenas" + }, + { + "_id": "America/Rainy_River", + "name": "America/Rainy_River" + }, + { + "_id": "America/Rankin_Inlet", + "name": "America/Rankin_Inlet" + }, + { + "_id": "America/Recife", + "name": "America/Recife" + }, + { + "_id": "America/Regina", + "name": "America/Regina" + }, + { + "_id": "America/Resolute", + "name": "America/Resolute" + }, + { + "_id": "America/Rio_Branco", + "name": "America/Rio_Branco" + }, + { + "_id": "America/Santarem", + "name": "America/Santarem" + }, + { + "_id": "America/Santiago", + "name": "America/Santiago" + }, + { + "_id": "America/Santo_Domingo", + "name": "America/Santo_Domingo" + }, + { + "_id": "America/Sao_Paulo", + "name": "America/Sao_Paulo" + }, + { + "_id": "America/Scoresbysund", + "name": "America/Scoresbysund" + }, + { + "_id": "America/Sitka", + "name": "America/Sitka" + }, + { + "_id": "America/St_Barthelemy", + "name": "America/St_Barthelemy" + }, + { + "_id": "America/St_Johns", + "name": "America/St_Johns" + }, + { + "_id": "America/St_Kitts", + "name": "America/St_Kitts" + }, + { + "_id": "America/St_Lucia", + "name": "America/St_Lucia" + }, + { + "_id": "America/St_Thomas", + "name": "America/St_Thomas" + }, + { + "_id": "America/St_Vincent", + "name": "America/St_Vincent" + }, + { + "_id": "America/Swift_Current", + "name": "America/Swift_Current" + }, + { + "_id": "America/Tegucigalpa", + "name": "America/Tegucigalpa" + }, + { + "_id": "America/Thule", + "name": "America/Thule" + }, + { + "_id": "America/Thunder_Bay", + "name": "America/Thunder_Bay" + }, + { + "_id": "America/Tijuana", + "name": "America/Tijuana" + }, + { + "_id": "America/Toronto", + "name": "America/Toronto" + }, + { + "_id": "America/Tortola", + "name": "America/Tortola" + }, + { + "_id": "America/Vancouver", + "name": "America/Vancouver" + }, + { + "_id": "America/Whitehorse", + "name": "America/Whitehorse" + }, + { + "_id": "America/Winnipeg", + "name": "America/Winnipeg" + }, + { + "_id": "America/Yakutat", + "name": "America/Yakutat" + }, + { + "_id": "America/Yellowknife", + "name": "America/Yellowknife" + }, + { + "_id": "Antarctica/Casey", + "name": "Antarctica/Casey" + }, + { + "_id": "Antarctica/Davis", + "name": "Antarctica/Davis" + }, + { + "_id": "Antarctica/DumontDUrville", + "name": "Antarctica/DumontDUrville" + }, + { + "_id": "Antarctica/Macquarie", + "name": "Antarctica/Macquarie" + }, + { + "_id": "Antarctica/Mawson", + "name": "Antarctica/Mawson" + }, + { + "_id": "Antarctica/McMurdo", + "name": "Antarctica/McMurdo" + }, + { + "_id": "Antarctica/Palmer", + "name": "Antarctica/Palmer" + }, + { + "_id": "Antarctica/Rothera", + "name": "Antarctica/Rothera" + }, + { + "_id": "Antarctica/Syowa", + "name": "Antarctica/Syowa" + }, + { + "_id": "Antarctica/Troll", + "name": "Antarctica/Troll" + }, + { + "_id": "Antarctica/Vostok", + "name": "Antarctica/Vostok" + }, + { + "_id": "Arctic/Longyearbyen", + "name": "Arctic/Longyearbyen" + }, + { + "_id": "Asia/Aden", + "name": "Asia/Aden" + }, + { + "_id": "Asia/Almaty", + "name": "Asia/Almaty" + }, + { + "_id": "Asia/Amman", + "name": "Asia/Amman" + }, + { + "_id": "Asia/Anadyr", + "name": "Asia/Anadyr" + }, + { + "_id": "Asia/Aqtau", + "name": "Asia/Aqtau" + }, + { + "_id": "Asia/Aqtobe", + "name": "Asia/Aqtobe" + }, + { + "_id": "Asia/Ashgabat", + "name": "Asia/Ashgabat" + }, + { + "_id": "Asia/Atyrau", + "name": "Asia/Atyrau" + }, + { + "_id": "Asia/Baghdad", + "name": "Asia/Baghdad" + }, + { + "_id": "Asia/Bahrain", + "name": "Asia/Bahrain" + }, + { + "_id": "Asia/Baku", + "name": "Asia/Baku" + }, + { + "_id": "Asia/Bangkok", + "name": "Asia/Bangkok" + }, + { + "_id": "Asia/Barnaul", + "name": "Asia/Barnaul" + }, + { + "_id": "Asia/Beirut", + "name": "Asia/Beirut" + }, + { + "_id": "Asia/Bishkek", + "name": "Asia/Bishkek" + }, + { + "_id": "Asia/Brunei", + "name": "Asia/Brunei" + }, + { + "_id": "Asia/Chita", + "name": "Asia/Chita" + }, + { + "_id": "Asia/Choibalsan", + "name": "Asia/Choibalsan" + }, + { + "_id": "Asia/Colombo", + "name": "Asia/Colombo" + }, + { + "_id": "Asia/Damascus", + "name": "Asia/Damascus" + }, + { + "_id": "Asia/Dhaka", + "name": "Asia/Dhaka" + }, + { + "_id": "Asia/Dili", + "name": "Asia/Dili" + }, + { + "_id": "Asia/Dubai", + "name": "Asia/Dubai" + }, + { + "_id": "Asia/Dushanbe", + "name": "Asia/Dushanbe" + }, + { + "_id": "Asia/Famagusta", + "name": "Asia/Famagusta" + }, + { + "_id": "Asia/Gaza", + "name": "Asia/Gaza" + }, + { + "_id": "Asia/Hebron", + "name": "Asia/Hebron" + }, + { + "_id": "Asia/Ho_Chi_Minh", + "name": "Asia/Ho_Chi_Minh" + }, + { + "_id": "Asia/Hong_Kong", + "name": "Asia/Hong_Kong" + }, + { + "_id": "Asia/Hovd", + "name": "Asia/Hovd" + }, + { + "_id": "Asia/Irkutsk", + "name": "Asia/Irkutsk" + }, + { + "_id": "Asia/Jakarta", + "name": "Asia/Jakarta" + }, + { + "_id": "Asia/Jayapura", + "name": "Asia/Jayapura" + }, + { + "_id": "Asia/Jerusalem", + "name": "Asia/Jerusalem" + }, + { + "_id": "Asia/Kabul", + "name": "Asia/Kabul" + }, + { + "_id": "Asia/Kamchatka", + "name": "Asia/Kamchatka" + }, + { + "_id": "Asia/Karachi", + "name": "Asia/Karachi" + }, + { + "_id": "Asia/Kathmandu", + "name": "Asia/Kathmandu" + }, + { + "_id": "Asia/Khandyga", + "name": "Asia/Khandyga" + }, + { + "_id": "Asia/Kolkata", + "name": "Asia/Kolkata" + }, + { + "_id": "Asia/Krasnoyarsk", + "name": "Asia/Krasnoyarsk" + }, + { + "_id": "Asia/Kuala_Lumpur", + "name": "Asia/Kuala_Lumpur" + }, + { + "_id": "Asia/Kuching", + "name": "Asia/Kuching" + }, + { + "_id": "Asia/Kuwait", + "name": "Asia/Kuwait" + }, + { + "_id": "Asia/Macau", + "name": "Asia/Macau" + }, + { + "_id": "Asia/Magadan", + "name": "Asia/Magadan" + }, + { + "_id": "Asia/Makassar", + "name": "Asia/Makassar" + }, + { + "_id": "Asia/Manila", + "name": "Asia/Manila" + }, + { + "_id": "Asia/Muscat", + "name": "Asia/Muscat" + }, + { + "_id": "Asia/Nicosia", + "name": "Asia/Nicosia" + }, + { + "_id": "Asia/Novokuznetsk", + "name": "Asia/Novokuznetsk" + }, + { + "_id": "Asia/Novosibirsk", + "name": "Asia/Novosibirsk" + }, + { + "_id": "Asia/Omsk", + "name": "Asia/Omsk" + }, + { + "_id": "Asia/Oral", + "name": "Asia/Oral" + }, + { + "_id": "Asia/Phnom_Penh", + "name": "Asia/Phnom_Penh" + }, + { + "_id": "Asia/Pontianak", + "name": "Asia/Pontianak" + }, + { + "_id": "Asia/Pyongyang", + "name": "Asia/Pyongyang" + }, + { + "_id": "Asia/Qatar", + "name": "Asia/Qatar" + }, + { + "_id": "Asia/Qostanay", + "name": "Asia/Qostanay" + }, + { + "_id": "Asia/Qyzylorda", + "name": "Asia/Qyzylorda" + }, + { + "_id": "Asia/Riyadh", + "name": "Asia/Riyadh" + }, + { + "_id": "Asia/Sakhalin", + "name": "Asia/Sakhalin" + }, + { + "_id": "Asia/Samarkand", + "name": "Asia/Samarkand" + }, + { + "_id": "Asia/Seoul", + "name": "Asia/Seoul" + }, + { + "_id": "Asia/Shanghai", + "name": "Asia/Shanghai" + }, + { + "_id": "Asia/Singapore", + "name": "Asia/Singapore" + }, + { + "_id": "Asia/Srednekolymsk", + "name": "Asia/Srednekolymsk" + }, + { + "_id": "Asia/Taipei", + "name": "Asia/Taipei" + }, + { + "_id": "Asia/Tashkent", + "name": "Asia/Tashkent" + }, + { + "_id": "Asia/Tbilisi", + "name": "Asia/Tbilisi" + }, + { + "_id": "Asia/Tehran", + "name": "Asia/Tehran" + }, + { + "_id": "Asia/Thimphu", + "name": "Asia/Thimphu" + }, + { + "_id": "Asia/Tokyo", + "name": "Asia/Tokyo" + }, + { + "_id": "Asia/Tomsk", + "name": "Asia/Tomsk" + }, + { + "_id": "Asia/Ulaanbaatar", + "name": "Asia/Ulaanbaatar" + }, + { + "_id": "Asia/Urumqi", + "name": "Asia/Urumqi" + }, + { + "_id": "Asia/Ust-Nera", + "name": "Asia/Ust-Nera" + }, + { + "_id": "Asia/Vientiane", + "name": "Asia/Vientiane" + }, + { + "_id": "Asia/Vladivostok", + "name": "Asia/Vladivostok" + }, + { + "_id": "Asia/Yakutsk", + "name": "Asia/Yakutsk" + }, + { + "_id": "Asia/Yangon", + "name": "Asia/Yangon" + }, + { + "_id": "Asia/Yekaterinburg", + "name": "Asia/Yekaterinburg" + }, + { + "_id": "Asia/Yerevan", + "name": "Asia/Yerevan" + }, + { + "_id": "Atlantic/Azores", + "name": "Atlantic/Azores" + }, + { + "_id": "Atlantic/Bermuda", + "name": "Atlantic/Bermuda" + }, + { + "_id": "Atlantic/Canary", + "name": "Atlantic/Canary" + }, + { + "_id": "Atlantic/Cape_Verde", + "name": "Atlantic/Cape_Verde" + }, + { + "_id": "Atlantic/Faroe", + "name": "Atlantic/Faroe" + }, + { + "_id": "Atlantic/Madeira", + "name": "Atlantic/Madeira" + }, + { + "_id": "Atlantic/Reykjavik", + "name": "Atlantic/Reykjavik" + }, + { + "_id": "Atlantic/South_Georgia", + "name": "Atlantic/South_Georgia" + }, + { + "_id": "Atlantic/St_Helena", + "name": "Atlantic/St_Helena" + }, + { + "_id": "Atlantic/Stanley", + "name": "Atlantic/Stanley" + }, + { + "_id": "Australia/Adelaide", + "name": "Australia/Adelaide" + }, + { + "_id": "Australia/Brisbane", + "name": "Australia/Brisbane" + }, + { + "_id": "Australia/Broken_Hill", + "name": "Australia/Broken_Hill" + }, + { + "_id": "Australia/Currie", + "name": "Australia/Currie" + }, + { + "_id": "Australia/Darwin", + "name": "Australia/Darwin" + }, + { + "_id": "Australia/Eucla", + "name": "Australia/Eucla" + }, + { + "_id": "Australia/Hobart", + "name": "Australia/Hobart" + }, + { + "_id": "Australia/Lindeman", + "name": "Australia/Lindeman" + }, + { + "_id": "Australia/Lord_Howe", + "name": "Australia/Lord_Howe" + }, + { + "_id": "Australia/Melbourne", + "name": "Australia/Melbourne" + }, + { + "_id": "Australia/Perth", + "name": "Australia/Perth" + }, + { + "_id": "Australia/Sydney", + "name": "Australia/Sydney" + }, + { + "_id": "Europe/Amsterdam", + "name": "Europe/Amsterdam" + }, + { + "_id": "Europe/Andorra", + "name": "Europe/Andorra" + }, + { + "_id": "Europe/Astrakhan", + "name": "Europe/Astrakhan" + }, + { + "_id": "Europe/Athens", + "name": "Europe/Athens" + }, + { + "_id": "Europe/Belgrade", + "name": "Europe/Belgrade" + }, + { + "_id": "Europe/Berlin", + "name": "Europe/Berlin" + }, + { + "_id": "Europe/Bratislava", + "name": "Europe/Bratislava" + }, + { + "_id": "Europe/Brussels", + "name": "Europe/Brussels" + }, + { + "_id": "Europe/Bucharest", + "name": "Europe/Bucharest" + }, + { + "_id": "Europe/Budapest", + "name": "Europe/Budapest" + }, + { + "_id": "Europe/Chisinau", + "name": "Europe/Chisinau" + }, + { + "_id": "Europe/Copenhagen", + "name": "Europe/Copenhagen" + }, + { + "_id": "Europe/Dublin", + "name": "Europe/Dublin" + }, + { + "_id": "Europe/Gibraltar", + "name": "Europe/Gibraltar" + }, + { + "_id": "Europe/Guernsey", + "name": "Europe/Guernsey" + }, + { + "_id": "Europe/Helsinki", + "name": "Europe/Helsinki" + }, + { + "_id": "Europe/Isle_of_Man", + "name": "Europe/Isle_of_Man" + }, + { + "_id": "Europe/Istanbul", + "name": "Europe/Istanbul" + }, + { + "_id": "Europe/Jersey", + "name": "Europe/Jersey" + }, + { + "_id": "Europe/Kaliningrad", + "name": "Europe/Kaliningrad" + }, + { + "_id": "Europe/Kirov", + "name": "Europe/Kirov" + }, + { + "_id": "Europe/Kyiv", + "name": "Europe/Kyiv" + }, + { + "_id": "Europe/Lisbon", + "name": "Europe/Lisbon" + }, + { + "_id": "Europe/Ljubljana", + "name": "Europe/Ljubljana" + }, + { + "_id": "Europe/London", + "name": "Europe/London" + }, + { + "_id": "Europe/Luxembourg", + "name": "Europe/Luxembourg" + }, + { + "_id": "Europe/Madrid", + "name": "Europe/Madrid" + }, + { + "_id": "Europe/Malta", + "name": "Europe/Malta" + }, + { + "_id": "Europe/Mariehamn", + "name": "Europe/Mariehamn" + }, + { + "_id": "Europe/Minsk", + "name": "Europe/Minsk" + }, + { + "_id": "Europe/Monaco", + "name": "Europe/Monaco" + }, + { + "_id": "Europe/Moscow", + "name": "Europe/Moscow" + }, + { + "_id": "Europe/Oslo", + "name": "Europe/Oslo" + }, + { + "_id": "Europe/Paris", + "name": "Europe/Paris" + }, + { + "_id": "Europe/Podgorica", + "name": "Europe/Podgorica" + }, + { + "_id": "Europe/Prague", + "name": "Europe/Prague" + }, + { + "_id": "Europe/Riga", + "name": "Europe/Riga" + }, + { + "_id": "Europe/Rome", + "name": "Europe/Rome" + }, + { + "_id": "Europe/Samara", + "name": "Europe/Samara" + }, + { + "_id": "Europe/San_Marino", + "name": "Europe/San_Marino" + }, + { + "_id": "Europe/Sarajevo", + "name": "Europe/Sarajevo" + }, + { + "_id": "Europe/Saratov", + "name": "Europe/Saratov" + }, + { + "_id": "Europe/Simferopol", + "name": "Europe/Simferopol" + }, + { + "_id": "Europe/Skopje", + "name": "Europe/Skopje" + }, + { + "_id": "Europe/Sofia", + "name": "Europe/Sofia" + }, + { + "_id": "Europe/Stockholm", + "name": "Europe/Stockholm" + }, + { + "_id": "Europe/Tallinn", + "name": "Europe/Tallinn" + }, + { + "_id": "Europe/Tirane", + "name": "Europe/Tirane" + }, + { + "_id": "Europe/Ulyanovsk", + "name": "Europe/Ulyanovsk" + }, + { + "_id": "Europe/Uzhgorod", + "name": "Europe/Uzhgorod" + }, + { + "_id": "Europe/Vaduz", + "name": "Europe/Vaduz" + }, + { + "_id": "Europe/Vatican", + "name": "Europe/Vatican" + }, + { + "_id": "Europe/Vienna", + "name": "Europe/Vienna" + }, + { + "_id": "Europe/Vilnius", + "name": "Europe/Vilnius" + }, + { + "_id": "Europe/Volgograd", + "name": "Europe/Volgograd" + }, + { + "_id": "Europe/Warsaw", + "name": "Europe/Warsaw" + }, + { + "_id": "Europe/Zagreb", + "name": "Europe/Zagreb" + }, + { + "_id": "Europe/Zaporizhzhia", + "name": "Europe/Zaporizhzhia" + }, + { + "_id": "Europe/Zurich", + "name": "Europe/Zurich" + }, + { + "_id": "Indian/Antananarivo", + "name": "Indian/Antananarivo" + }, + { + "_id": "Indian/Chagos", + "name": "Indian/Chagos" + }, + { + "_id": "Indian/Christmas", + "name": "Indian/Christmas" + }, + { + "_id": "Indian/Cocos", + "name": "Indian/Cocos" + }, + { + "_id": "Indian/Comoro", + "name": "Indian/Comoro" + }, + { + "_id": "Indian/Kerguelen", + "name": "Indian/Kerguelen" + }, + { + "_id": "Indian/Mahe", + "name": "Indian/Mahe" + }, + { + "_id": "Indian/Maldives", + "name": "Indian/Maldives" + }, + { + "_id": "Indian/Mauritius", + "name": "Indian/Mauritius" + }, + { + "_id": "Indian/Mayotte", + "name": "Indian/Mayotte" + }, + { + "_id": "Indian/Reunion", + "name": "Indian/Reunion" + }, + { + "_id": "Pacific/Apia", + "name": "Pacific/Apia" + }, + { + "_id": "Pacific/Auckland", + "name": "Pacific/Auckland" + }, + { + "_id": "Pacific/Bougainville", + "name": "Pacific/Bougainville" + }, + { + "_id": "Pacific/Chatham", + "name": "Pacific/Chatham" + }, + { + "_id": "Pacific/Chuuk", + "name": "Pacific/Chuuk" + }, + { + "_id": "Pacific/Easter", + "name": "Pacific/Easter" + }, + { + "_id": "Pacific/Efate", + "name": "Pacific/Efate" + }, + { + "_id": "Pacific/Enderbury", + "name": "Pacific/Enderbury" + }, + { + "_id": "Pacific/Fakaofo", + "name": "Pacific/Fakaofo" + }, + { + "_id": "Pacific/Fiji", + "name": "Pacific/Fiji" + }, + { + "_id": "Pacific/Funafuti", + "name": "Pacific/Funafuti" + }, + { + "_id": "Pacific/Galapagos", + "name": "Pacific/Galapagos" + }, + { + "_id": "Pacific/Gambier", + "name": "Pacific/Gambier" + }, + { + "_id": "Pacific/Guadalcanal", + "name": "Pacific/Guadalcanal" + }, + { + "_id": "Pacific/Guam", + "name": "Pacific/Guam" + }, + { + "_id": "Pacific/Honolulu", + "name": "Pacific/Honolulu" + }, + { + "_id": "Pacific/Kiritimati", + "name": "Pacific/Kiritimati" + }, + { + "_id": "Pacific/Kosrae", + "name": "Pacific/Kosrae" + }, + { + "_id": "Pacific/Kwajalein", + "name": "Pacific/Kwajalein" + }, + { + "_id": "Pacific/Majuro", + "name": "Pacific/Majuro" + }, + { + "_id": "Pacific/Marquesas", + "name": "Pacific/Marquesas" + }, + { + "_id": "Pacific/Midway", + "name": "Pacific/Midway" + }, + { + "_id": "Pacific/Nauru", + "name": "Pacific/Nauru" + }, + { + "_id": "Pacific/Niue", + "name": "Pacific/Niue" + }, + { + "_id": "Pacific/Norfolk", + "name": "Pacific/Norfolk" + }, + { + "_id": "Pacific/Noumea", + "name": "Pacific/Noumea" + }, + { + "_id": "Pacific/Pago_Pago", + "name": "Pacific/Pago_Pago" + }, + { + "_id": "Pacific/Palau", + "name": "Pacific/Palau" + }, + { + "_id": "Pacific/Pitcairn", + "name": "Pacific/Pitcairn" + }, + { + "_id": "Pacific/Pohnpei", + "name": "Pacific/Pohnpei" + }, + { + "_id": "Pacific/Port_Moresby", + "name": "Pacific/Port_Moresby" + }, + { + "_id": "Pacific/Rarotonga", + "name": "Pacific/Rarotonga" + }, + { + "_id": "Pacific/Saipan", + "name": "Pacific/Saipan" + }, + { + "_id": "Pacific/Tahiti", + "name": "Pacific/Tahiti" + }, + { + "_id": "Pacific/Tarawa", + "name": "Pacific/Tarawa" + }, + { + "_id": "Pacific/Tongatapu", + "name": "Pacific/Tongatapu" + }, + { + "_id": "Pacific/Wake", + "name": "Pacific/Wake" + }, + { + "_id": "Pacific/Wallis", + "name": "Pacific/Wallis" + } ] diff --git a/Client/src/Utils/toastUtils.jsx b/Client/src/Utils/toastUtils.jsx index 0383ba466..dbec9117e 100644 --- a/Client/src/Utils/toastUtils.jsx +++ b/Client/src/Utils/toastUtils.jsx @@ -12,40 +12,40 @@ import Alert from "../Components/Alert"; */ export const createToast = ({ - variant = "info", - title, - body, - hasIcon = false, - config = {}, + variant = "info", + title, + body, + hasIcon = false, + config = {}, }) => { - const toastConfig = { - position: "top-right", - autoClose: 3000, - hideProgressBar: true, - closeButton: false, - transition: Slide, - ...config, - }; + const toastConfig = { + position: "top-right", + autoClose: 3000, + hideProgressBar: true, + closeButton: false, + transition: Slide, + ...config, + }; - toast( - ({ closeToast }) => ( - - ), - toastConfig - ); + toast( + ({ closeToast }) => ( + + ), + toastConfig + ); }; createToast.propTypes = { - variant: PropTypes.oneOf(["info", "error", "warning"]), - title: PropTypes.string, - body: PropTypes.string.isRequired, - hasIcon: PropTypes.bool, - config: PropTypes.object, + variant: PropTypes.oneOf(["info", "error", "warning"]), + title: PropTypes.string, + body: PropTypes.string.isRequired, + hasIcon: PropTypes.bool, + config: PropTypes.object, }; diff --git a/Client/src/Validation/error.js b/Client/src/Validation/error.js new file mode 100644 index 000000000..e205ce7d1 --- /dev/null +++ b/Client/src/Validation/error.js @@ -0,0 +1,25 @@ +const buildErrors = (prev, id, error) => { + const updatedErrors = { ...prev }; + if (error) { + updatedErrors[id] = error.details[0].message?? "Validation error"; + } else { + delete updatedErrors[id]; + } + return updatedErrors; +}; + +const hasValidationErrors = (form, validation, setErrors) => { + const { error } = validation.validate(form, { + abortEarly: false, + }); + if (error) { + const newErrors = {}; + error.details.forEach((err) => { + newErrors[err.path[0]] = err.message?? "Validation error"; + }); + setErrors(newErrors); + return true; + } + return false; +}; +export { buildErrors, hasValidationErrors }; diff --git a/Client/src/Validation/validation.js b/Client/src/Validation/validation.js index f21f2e09b..a6ec7688c 100644 --- a/Client/src/Validation/validation.js +++ b/Client/src/Validation/validation.js @@ -2,145 +2,169 @@ import joi from "joi"; import dayjs from "dayjs"; const nameSchema = joi - .string() - .max(50) - .trim() - .pattern(/^[A-Za-z]+$/) - .messages({ - "string.empty": "Name is required", - "string.max": "Name must be less than 50 characters long", - "string.pattern.base": "Name must contain only letters", - }); + .string() + .max(50) + .trim() + .pattern(/^[A-Za-z]+$/) + .messages({ + "string.empty": "Name is required", + "string.max": "Name must be less than 50 characters long", + "string.pattern.base": "Name must contain only letters", + }); const passwordSchema = joi - .string() - .trim() - .min(8) - .messages({ - "string.empty": "Password is required", - "string.min": "Password must be at least 8 characters long", - }) - .custom((value, helpers) => { - if (!/[A-Z]/.test(value)) { - return helpers.message( - "Password must contain at least one uppercase letter" - ); - } - if (!/[a-z]/.test(value)) { - return helpers.message( - "Password must contain at least one lowercase letter" - ); - } - if (!/\d/.test(value)) { - return helpers.message("Password must contain at least one number"); - } - if (!/[!@#$%^&*]/.test(value)) { - return helpers.message( - "Password must contain at least one special character" - ); - } + .string() + .trim() + .min(8) + .messages({ + "string.empty": "Password is required", + "string.min": "Password must be at least 8 characters long", + }) + .custom((value, helpers) => { + if (!/[A-Z]/.test(value)) { + return helpers.message("Password must contain at least one uppercase letter"); + } + if (!/[a-z]/.test(value)) { + return helpers.message("Password must contain at least one lowercase letter"); + } + if (!/\d/.test(value)) { + return helpers.message("Password must contain at least one number"); + } + if (!/[!@#$%^&*]/.test(value)) { + return helpers.message("Password must contain at least one special character"); + } - return value; - }); + return value; + }); const credentials = joi.object({ - firstName: nameSchema, - lastName: nameSchema, - email: joi - .string() - .trim() - .email({ tlds: { allow: false } }) - .custom((value, helpers) => { - const lowercasedValue = value.toLowerCase(); - if (value !== lowercasedValue) { - return helpers.message("Email must be in lowercase"); - } - return lowercasedValue; - }) - .messages({ - "string.empty": "Email is required", - "string.email": "Must be a valid email address", - }), - password: passwordSchema, - newPassword: passwordSchema, - confirm: joi - .string() - .trim() - .messages({ - "string.empty": "Password confirmation is required", - }) - .custom((value, helpers) => { - const { password } = helpers.prefs.context; - if (value !== password) { - return helpers.message("Passwords do not match"); - } - return value; - }), - role: joi.array(), - teamId: joi.string().allow("").optional(), - inviteToken: joi.string().allow(""), + firstName: nameSchema, + lastName: nameSchema, + email: joi + .string() + .trim() + .email({ tlds: { allow: false } }) + .custom((value, helpers) => { + const lowercasedValue = value.toLowerCase(); + if (value !== lowercasedValue) { + return helpers.message("Email must be in lowercase"); + } + return lowercasedValue; + }) + .messages({ + "string.empty": "Email is required", + "string.email": "Must be a valid email address", + }), + password: passwordSchema, + newPassword: passwordSchema, + confirm: joi + .string() + .trim() + .messages({ + "string.empty": "Password confirmation is required", + }) + .custom((value, helpers) => { + const { password } = helpers.prefs.context; + if (value !== password) { + return helpers.message("Passwords do not match"); + } + return value; + }), + role: joi.array(), + teamId: joi.string().allow("").optional(), + inviteToken: joi.string().allow(""), }); const monitorValidation = joi.object({ - url: joi.string().uri({ allowRelative: true }).trim().messages({ - "string.empty": "This field is required.", - "string.uri": "The URL you provided is not valid.", - }), - name: joi.string().trim().max(50).allow("").messages({ - "string.max": "This field should not exceed the 50 characters limit.", - }), - type: joi - .string() - .trim() - .messages({ "string.empty": "This field is required." }), - interval: joi.number().messages({ - "number.base": "Frequency must be a number.", - "any.required": "Frequency is required.", - }), + url: joi.string().uri({ allowRelative: true }).trim().messages({ + "string.empty": "This field is required.", + "string.uri": "The URL you provided is not valid.", + }), + name: joi.string().trim().max(50).allow("").messages({ + "string.max": "This field should not exceed the 50 characters limit.", + }), + type: joi.string().trim().messages({ "string.empty": "This field is required." }), + interval: joi.number().messages({ + "number.base": "Frequency must be a number.", + "any.required": "Frequency is required.", + }), }); const imageValidation = joi.object({ - type: joi.string().valid("image/jpeg", "image/png").messages({ - "any.only": "Invalid file format.", - "string.empty": "File type required.", - }), - size: joi - .number() - .max(3 * 1024 * 1024) - .messages({ - "number.base": "File size must be a number.", - "number.max": "File size must be less than 3 MB.", - "number.empty": "File size required.", - }), + type: joi.string().valid("image/jpeg", "image/png").messages({ + "any.only": "Invalid file format.", + "string.empty": "File type required.", + }), + size: joi + .number() + .max(3 * 1024 * 1024) + .messages({ + "number.base": "File size must be a number.", + "number.max": "File size must be less than 3 MB.", + "number.empty": "File size required.", + }), }); const settingsValidation = joi.object({ - ttl: joi.number().required().messages({ - "string.empty": "TTL is required", - }), + ttl: joi.number().required().messages({ + "string.empty": "TTL is required", + }), }); const dayjsValidator = (value, helpers) => { - if (!dayjs(value).isValid()) { - return helpers.error("any.invalid"); - } - return value; + if (!dayjs(value).isValid()) { + return helpers.error("any.invalid"); + } + return value; }; const maintenanceWindowValidation = joi.object({ - repeat: joi.string(), - startDate: joi.custom(dayjsValidator, "Day.js date validation"), - startTime: joi.custom(dayjsValidator, "Day.js date validation"), - duration: joi.number().integer().min(0), - durationUnit: joi.string(), - name: joi.string(), - monitors: joi.array().min(1), + repeat: joi.string(), + startDate: joi.custom(dayjsValidator, "Day.js date validation"), + startTime: joi.custom(dayjsValidator, "Day.js date validation"), + duration: joi.number().integer().min(0), + durationUnit: joi.string(), + name: joi.string(), + monitors: joi.array().min(1), +}); + +const advancedSettingsValidation = joi.object({ + apiBaseUrl: joi.string().uri({ allowRelative: true }).trim().messages({ + "string.empty": "API base url is required.", + "string.uri": "The URL you provided is not valid.", + }), + logLevel: joi.string().valid("debug", "none", "error", "warn").allow(""), + systemEmailHost: joi.string().allow(''), + systemEmailPort: joi.string().allow(''), + systemEmailAddress: joi + .string().allow(''), + systemEmailPassword: joi.string().allow(''), + jwtTTL: joi + .string() + .trim() + .messages({ + "string.empty": "JWT TTL is required." + }), + dbType: joi.string().trim().messages({ + "string.empty": "DB type is required", + }), + redisHost: joi.string().trim().messages({ + "string.empty": "Redis host is required", + }), + redisPort: joi.string().allow('').custom((value, helpers) => { + if(value && isNaN(parseInt(value))){ + return helpers.message("Redit port must be a number") + } + return value + }), + pagespeedApiKey: joi.string().allow('') }); export { - credentials, - imageValidation, - monitorValidation, - settingsValidation, - maintenanceWindowValidation, + credentials, + imageValidation, + monitorValidation, + settingsValidation, + maintenanceWindowValidation, + advancedSettingsValidation, }; diff --git a/Client/src/index.css b/Client/src/index.css index 7293eedfb..369e5d16b 100644 --- a/Client/src/index.css +++ b/Client/src/index.css @@ -1,94 +1,94 @@ @import url("https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap"); * { - box-sizing: border-box; + box-sizing: border-box; } html { - scroll-behavior: smooth; + scroll-behavior: smooth; } :root { - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; - /* Generalized Stylings */ - --env-var-radius-1: 4px; - --env-var-radius-2: 8px; + /* Generalized Stylings */ + --env-var-radius-1: 4px; + --env-var-radius-2: 8px; - --env-var-width-1: 100vw; - --env-var-width-2: 360px; + --env-var-width-1: 100vw; + --env-var-width-2: 360px; - --env-var-height-1: 100vh; - --env-var-height-2: 34px; + --env-var-height-1: 100vh; + --env-var-height-2: 34px; - --env-var-nav-bar-height: 70px; - --env-var-side-bar-width: 250px; + --env-var-nav-bar-height: 70px; + --env-var-side-bar-width: 250px; - --env-var-spacing-1: 12px; - --env-var-spacing-1-plus: 16px; - --env-var-spacing-1-minus: 10px; - --env-var-spacing-2: 24px; - --env-var-spacing-3: 32px; - --env-var-spacing-4: 40px; - --env-var-spacing-5: 65px; + --env-var-spacing-1: 12px; + --env-var-spacing-1-plus: 16px; + --env-var-spacing-1-minus: 10px; + --env-var-spacing-2: 24px; + --env-var-spacing-3: 32px; + --env-var-spacing-4: 40px; + --env-var-spacing-5: 65px; - --env-var-font-size-small: 11px; - --env-var-font-size-small-plus: 12px; - --env-var-font-size-medium: 13px; - --env-var-font-size-medium-plus: 14px; - --env-var-font-size-large: 16px; - --env-var-font-size-large-plus: 22px; - --env-var-font-size-xlarge: 30px; + --env-var-font-size-small: 11px; + --env-var-font-size-small-plus: 12px; + --env-var-font-size-medium: 13px; + --env-var-font-size-medium-plus: 14px; + --env-var-font-size-large: 16px; + --env-var-font-size-large-plus: 22px; + --env-var-font-size-xlarge: 30px; - --env-var-img-width-1: 20px; - --env-var-img-width-2: 16px; - --env-var-img-width-3: 12px; + --env-var-img-width-1: 20px; + --env-var-img-width-2: 16px; + --env-var-img-width-3: 12px; - --env-var-default-1: 24px; - --env-var-default-2: 40px; + --env-var-default-1: 24px; + --env-var-default-2: 40px; - --env-var-shadow-1: 0px 4px 24px -4px rgba(16, 24, 40, 0.08), - 0px 3px 3px -3px rgba(16, 24, 40, 0.03); + --env-var-shadow-1: 0px 4px 24px -4px rgba(16, 24, 40, 0.08), + 0px 3px 3px -3px rgba(16, 24, 40, 0.03); } .MuiInputBase-root.Mui-disabled input { - cursor: not-allowed; + cursor: not-allowed; } .Toastify__toast-container { - width: auto; + width: auto; } .Toastify__toast-body .alert { - min-width: 150px; - padding: 5px 10px; - align-items: center; + min-width: 150px; + padding: 5px 10px; + align-items: center; } .Toastify [class^="Toastify__toast"] { - padding: 0; - margin: 0; + padding: 0; + margin: 0; } .Toastify__toast { - min-height: 0; - border-radius: 4px; + min-height: 0; + border-radius: 4px; } .Toastify [class*="Toastify__toast-theme"] { - background-color: transparent; + background-color: transparent; } .MuiTouchRipple-root { - display: none; + display: none; } @keyframes ripple { - from { - opacity: 1; - transform: scale(0); - } - to { - opacity: 0; - transform: scale(2); - } + from { + opacity: 1; + transform: scale(0); + } + to { + opacity: 0; + transform: scale(2); + } } diff --git a/Client/src/main.jsx b/Client/src/main.jsx index 4c04a65eb..c07cad40b 100644 --- a/Client/src/main.jsx +++ b/Client/src/main.jsx @@ -10,13 +10,16 @@ import { networkService } from "./Utils/NetworkService"; export { networkService }; ReactDOM.createRoot(document.getElementById("root")).render( - - - - - - - - - + + + + + + + + + ); diff --git a/Client/src/store.js b/Client/src/store.js index 644da5e62..0b4c46ce8 100644 --- a/Client/src/store.js +++ b/Client/src/store.js @@ -9,45 +9,41 @@ import storage from "redux-persist/lib/storage"; import { persistReducer, persistStore, createTransform } from "redux-persist"; const authTransform = createTransform( - (inboundState) => { - const { profileImage, ...rest } = inboundState; - return rest; - }, - // No transformation on rehydration - null, - // Only applies to auth - { whitelist: ["auth"] } + (inboundState) => { + const { profileImage, ...rest } = inboundState; + return rest; + }, + // No transformation on rehydration + null, + // Only applies to auth + { whitelist: ["auth"] } ); const persistConfig = { - key: "root", - storage, - whitelist: ["auth", "monitors", "pageSpeed", "ui", "settings"], - transforms: [authTransform], + key: "root", + storage, + whitelist: ["auth", "monitors", "pageSpeed", "ui", "settings"], + transforms: [authTransform], }; const rootReducer = combineReducers({ - uptimeMonitors: uptimeMonitorsReducer, - auth: authReducer, - pageSpeedMonitors: pageSpeedMonitorReducer, - ui: uiReducer, - settings: settingsReducer, + uptimeMonitors: uptimeMonitorsReducer, + auth: authReducer, + pageSpeedMonitors: pageSpeedMonitorReducer, + ui: uiReducer, + settings: settingsReducer, }); const persistedReducer = persistReducer(persistConfig, rootReducer); export const store = configureStore({ - reducer: persistedReducer, - middleware: (getDefaultMiddleware) => - getDefaultMiddleware({ - serializableCheck: { - ignoredActions: [ - "persist/PERSIST", - "persist/REHYDRATE", - "persist/REGISTER", - ], - }, - }), + reducer: persistedReducer, + middleware: (getDefaultMiddleware) => + getDefaultMiddleware({ + serializableCheck: { + ignoredActions: ["persist/PERSIST", "persist/REHYDRATE", "persist/REGISTER"], + }, + }), }); export const persistor = persistStore(store); diff --git a/Client/vite.config.js b/Client/vite.config.js index 9a3ca3842..22fcd51fa 100644 --- a/Client/vite.config.js +++ b/Client/vite.config.js @@ -1,11 +1,11 @@ -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' -import svgr from 'vite-plugin-svgr'; +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; +import svgr from "vite-plugin-svgr"; // https://vitejs.dev/config/ export default defineConfig({ - plugins: [svgr(), react()], - optimizeDeps: { - include: ['@mui/material/Tooltip', '@emotion/styled'], - }, -}) \ No newline at end of file + plugins: [svgr(), react()], + optimizeDeps: { + include: ["@mui/material/Tooltip", "@emotion/styled"], + }, +}); diff --git a/Docker/dev/build_images.sh b/Docker/dev/build_images.sh index 2de827dfe..f6171089c 100755 --- a/Docker/dev/build_images.sh +++ b/Docker/dev/build_images.sh @@ -1,24 +1,26 @@ - #!/bin/bash # Change directory to root Server directory for correct Docker Context +cd "$(dirname "$0")" cd ../.. -#Client -client="./Docker/dev/client.Dockerfile" +# Define an array of services and their Dockerfiles +declare -A services=( + ["uptime_client"]="./Docker/dev/client.Dockerfile" + ["uptime_database_mongo"]="./Docker/dev/mongoDB.Dockerfile" + ["uptime_redis"]="./Docker/dev/redis.Dockerfile" + ["uptime_server"]="./Docker/dev/server.Dockerfile" +) -# MongoDB -mongoDB="./Docker/dev/mongoDB.Dockerfile" +# Loop through each service and build the corresponding image +for service in "${!services[@]}"; do + docker build -f "${services[$service]}" -t "$service" . + + ## Check if the build succeeded + if [ $? -ne 0 ]; then + echo "Error building $service image. Exiting..." + exit 1 + fi +done -# Redis -redis="./Docker/dev/redis.Dockerfile" - -# Server -server="./Docker/dev/server.Dockerfile" - -docker build -f $client -t uptime_client . -docker build -f $mongoDB -t uptime_database_mongo . -docker build -f $redis -t uptime_redis . -docker build -f $server -t uptime_server . - -echo "All images built" \ No newline at end of file +echo "All images built successfully" diff --git a/Docker/dist/build_images.sh b/Docker/dist/build_images.sh index 1df514e7e..a5ec3eec9 100755 --- a/Docker/dist/build_images.sh +++ b/Docker/dist/build_images.sh @@ -1,24 +1,26 @@ - #!/bin/bash # Change directory to root Server directory for correct Docker Context +cd "$(dirname "$0")" cd ../.. -#Client -client="./Docker/dist/client.Dockerfile" +# Define an array of services and their Dockerfiles +declare -A services=( + ["bluewave/uptime_client"]="./Docker/dist/client.Dockerfile" + ["bluewave/database_mongo"]="./Docker/dist/mongoDB.Dockerfile" + ["bluewave/uptime_redis"]="./Docker/dist/redis.Dockerfile" + ["bluewave/uptime_server"]="./Docker/dist/server.Dockerfile" +) -# MongoDB -mongoDB="./Docker/dist/mongoDB.Dockerfile" +# Loop through each service and build the corresponding image +for service in "${!services[@]}"; do + docker build -f "${services[$service]}" -t "$service" . + + # Check if the build succeeded + if [ $? -ne 0 ]; then + echo "Error building $service image. Exiting..." + exit 1 + fi +done -# Redis -redis="./Docker/dist/redis.Dockerfile" - -# Server -server="./Docker/dist/server.Dockerfile" - -docker build -f $client -t dist_uptime_client . -docker build -f $mongoDB -t dist_uptime_database_mongo . -docker build -f $redis -t dist_uptime_redis . -docker build -f $server -t dist_uptime_server . - -echo "All images built" \ No newline at end of file +echo "All images built successfully" \ No newline at end of file diff --git a/Docker/prod/build_images.sh b/Docker/prod/build_images.sh index 98d77e660..c879190bc 100755 --- a/Docker/prod/build_images.sh +++ b/Docker/prod/build_images.sh @@ -1,24 +1,26 @@ - #!/bin/bash # Change directory to root directory for correct Docker Context +cd "$(dirname "$0")" cd ../.. -#Client -client="./Docker/prod/client.Dockerfile" +# Define an array of services and their Dockerfiles +declare -A services=( + ["uptime_client"]="./Docker/prod/client.Dockerfile" + ["uptime_database_mongo"]="./Docker/prod/mongoDB.Dockerfile" + ["uptime_redis"]="./Docker/prod/redis.Dockerfile" + ["uptime_server"]="./Docker/prod/server.Dockerfile" +) -# MongoDB -mongoDB="./Docker/prod/mongoDB.Dockerfile" +# Loop through each service and build the corresponding image +for service in "${!services[@]}"; do + docker build -f "${services[$service]}" -t "$service" . + + # Check if the build succeeded + if [ $? -ne 0 ]; then + echo "Error building $service image. Exiting..." + exit 1 + fi +done -# Redis -redis="./Docker/prod/redis.Dockerfile" - -# Server -server="./Docker/prod/server.Dockerfile" - -docker build -f $client -t uptime_client . -docker build -f $mongoDB -t uptime_database_mongo . -docker build -f $redis -t uptime_redis . -docker build -f $server -t uptime_server . - -echo "All images built" \ No newline at end of file +echo "All images built successfully" \ No newline at end of file diff --git a/README.md b/README.md index 06c248c4b..aacaf4ea8 100644 --- a/README.md +++ b/README.md @@ -44,14 +44,13 @@ We've just launched our [Discussions](https://github.com/bluewave-labs/bluewave- **Roadmap (short term):** -- [ ] Memory, disk and CPU monitoring -- [ ] 3rd party integrations -- [ ] DNS monitoring -- [ ] SSL monitoring +We are actively developing **infrastructure monitoring** features for Uptime Manager, which will include comprehensive monitoring of memory, disk usage, and CPU performance. Our goal is to build a lightweight agent that runs on Linux servers, continuously collecting and transmitting health metrics to Uptime Manager, where the data will be visualized for real-time insights. **Roadmap (long term):** -- [ ] Status pages +- DNS monitoring +- SSL monitoring +- Status pages ## Tech stack diff --git a/Server/.nycrc b/Server/.nycrc index d7668b4c9..83b717bb5 100644 --- a/Server/.nycrc +++ b/Server/.nycrc @@ -1,6 +1,6 @@ { "all": true, - "include": ["controllers/*.js", "utils/*.js"], + "include": ["controllers/*.js", "utils/*.js", "service/*.js"], "exclude": ["**/*.test.js"], "reporter": ["html", "text", "lcov"], "sourceMap": false, diff --git a/Server/configs/db.js b/Server/configs/db.js index 153962e36..e42595b4c 100644 --- a/Server/configs/db.js +++ b/Server/configs/db.js @@ -1,15 +1,15 @@ const PORT = 5000; const connectDbAndRunServer = async (app, db) => { - try { - await db.connect(); - app.listen(PORT, () => { - console.log(`server started on port:${PORT}`); - }); - } catch (error) { - console.log("Failed to connect to DB"); - console.error(error); - } + try { + await db.connect(); + app.listen(PORT, () => { + console.log(`server started on port:${PORT}`); + }); + } catch (error) { + console.log("Failed to connect to DB"); + console.error(error); + } }; export { connectDbAndRunServer }; diff --git a/Server/controllers/checkController.js b/Server/controllers/checkController.js index fddf66949..a47f3f8c4 100644 --- a/Server/controllers/checkController.js +++ b/Server/controllers/checkController.js @@ -1,13 +1,13 @@ import { - createCheckParamValidation, - createCheckBodyValidation, - getChecksParamValidation, - getChecksQueryValidation, - getTeamChecksParamValidation, - getTeamChecksQueryValidation, - deleteChecksParamValidation, - deleteChecksByTeamIdParamValidation, - updateChecksTTLBodyValidation, + createCheckParamValidation, + createCheckBodyValidation, + getChecksParamValidation, + getChecksQueryValidation, + getTeamChecksParamValidation, + getTeamChecksQueryValidation, + deleteChecksParamValidation, + deleteChecksByTeamIdParamValidation, + updateChecksTTLBodyValidation, } from "../validation/joi.js"; import { successMessages } from "../utils/messages.js"; import jwt from "jsonwebtoken"; @@ -17,138 +17,138 @@ import { handleValidationError, handleError } from "./controllerUtils.js"; const SERVICE_NAME = "checkController"; const createCheck = async (req, res, next) => { - try { - await createCheckParamValidation.validateAsync(req.params); - await createCheckBodyValidation.validateAsync(req.body); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; - } + try { + await createCheckParamValidation.validateAsync(req.params); + await createCheckBodyValidation.validateAsync(req.body); + } catch (error) { + next(handleValidationError(error, SERVICE_NAME)); + return; + } - try { - const checkData = { ...req.body }; - const check = await req.db.createCheck(checkData); - return res - .status(200) - .json({ success: true, msg: successMessages.CHECK_CREATE, data: check }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "createCheck")); - } + try { + const checkData = { ...req.body }; + const check = await req.db.createCheck(checkData); + return res + .status(200) + .json({ success: true, msg: successMessages.CHECK_CREATE, data: check }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "createCheck")); + } }; const getChecks = async (req, res, next) => { - try { - await getChecksParamValidation.validateAsync(req.params); - await getChecksQueryValidation.validateAsync(req.query); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; - } + try { + await getChecksParamValidation.validateAsync(req.params); + await getChecksQueryValidation.validateAsync(req.query); + } catch (error) { + next(handleValidationError(error, SERVICE_NAME)); + return; + } - try { - const checks = await req.db.getChecks(req); - const checksCount = await req.db.getChecksCount(req); - return res.status(200).json({ - success: true, - msg: successMessages.CHECK_GET, - data: { checksCount, checks }, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "getChecks")); - } + try { + const checks = await req.db.getChecks(req); + const checksCount = await req.db.getChecksCount(req); + return res.status(200).json({ + success: true, + msg: successMessages.CHECK_GET, + data: { checksCount, checks }, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "getChecks")); + } }; const getTeamChecks = async (req, res, next) => { - try { - await getTeamChecksParamValidation.validateAsync(req.params); - await getTeamChecksQueryValidation.validateAsync(req.query); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; - } - try { - const checkData = await req.db.getTeamChecks(req); - return res.status(200).json({ - success: true, - msg: successMessages.CHECK_GET, - data: checkData, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "getTeamChecks")); - } + try { + await getTeamChecksParamValidation.validateAsync(req.params); + await getTeamChecksQueryValidation.validateAsync(req.query); + } catch (error) { + next(handleValidationError(error, SERVICE_NAME)); + return; + } + try { + const checkData = await req.db.getTeamChecks(req); + return res.status(200).json({ + success: true, + msg: successMessages.CHECK_GET, + data: checkData, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "getTeamChecks")); + } }; const deleteChecks = async (req, res, next) => { - try { - await deleteChecksParamValidation.validateAsync(req.params); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; - } + try { + await deleteChecksParamValidation.validateAsync(req.params); + } catch (error) { + next(handleValidationError(error, SERVICE_NAME)); + return; + } - try { - const deletedCount = await req.db.deleteChecks(req.params.monitorId); - return res.status(200).json({ - success: true, - msg: successMessages.CHECK_DELETE, - data: { deletedCount }, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "deleteChecks")); - } + try { + const deletedCount = await req.db.deleteChecks(req.params.monitorId); + return res.status(200).json({ + success: true, + msg: successMessages.CHECK_DELETE, + data: { deletedCount }, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "deleteChecks")); + } }; const deleteChecksByTeamId = async (req, res, next) => { - try { - await deleteChecksByTeamIdParamValidation.validateAsync(req.params); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; - } + try { + await deleteChecksByTeamIdParamValidation.validateAsync(req.params); + } catch (error) { + next(handleValidationError(error, SERVICE_NAME)); + return; + } - try { - const deletedCount = await req.db.deleteChecksByTeamId(req.params.teamId); - return res.status(200).json({ - success: true, - msg: successMessages.CHECK_DELETE, - data: { deletedCount }, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "deleteChecksByTeamId")); - } + try { + const deletedCount = await req.db.deleteChecksByTeamId(req.params.teamId); + return res.status(200).json({ + success: true, + msg: successMessages.CHECK_DELETE, + data: { deletedCount }, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "deleteChecksByTeamId")); + } }; const updateChecksTTL = async (req, res, next) => { - const SECONDS_PER_DAY = 86400; + const SECONDS_PER_DAY = 86400; - try { - await updateChecksTTLBodyValidation.validateAsync(req.body); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; - } + try { + await updateChecksTTLBodyValidation.validateAsync(req.body); + } catch (error) { + next(handleValidationError(error, SERVICE_NAME)); + return; + } - try { - // Get user's teamId - const token = getTokenFromHeaders(req.headers); - const { jwtSecret } = req.settingsService.getSettings(); - const { teamId } = jwt.verify(token, jwtSecret); - const ttl = parseInt(req.body.ttl, 10) * SECONDS_PER_DAY; - await req.db.updateChecksTTL(teamId, ttl); - return res.status(200).json({ - success: true, - msg: successMessages.CHECK_UPDATE_TTL, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "updateTTL")); - } + try { + // Get user's teamId + const token = getTokenFromHeaders(req.headers); + const { jwtSecret } = req.settingsService.getSettings(); + const { teamId } = jwt.verify(token, jwtSecret); + const ttl = parseInt(req.body.ttl, 10) * SECONDS_PER_DAY; + await req.db.updateChecksTTL(teamId, ttl); + return res.status(200).json({ + success: true, + msg: successMessages.CHECK_UPDATE_TTL, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "updateTTL")); + } }; export { - createCheck, - getChecks, - getTeamChecks, - deleteChecks, - deleteChecksByTeamId, - updateChecksTTL, + createCheck, + getChecks, + getTeamChecks, + deleteChecks, + deleteChecksByTeamId, + updateChecksTTL, }; diff --git a/Server/controllers/controllerUtils.js b/Server/controllers/controllerUtils.js index 313c2a09a..38c45b3cb 100644 --- a/Server/controllers/controllerUtils.js +++ b/Server/controllers/controllerUtils.js @@ -12,32 +12,15 @@ const handleError = (error, serviceName, method, status = 500) => { return error; }; -const fetchMonitorCertificate = async (tls, monitor) => { - return new Promise((resolve, reject) => { +const fetchMonitorCertificate = async (sslChecker, monitor) => { + try { const monitorUrl = new URL(monitor.url); const hostname = monitorUrl.hostname; - try { - let socket = tls.connect( - { - port: 443, - host: hostname, - servername: hostname, // this is required in case the server enabled SNI - }, - () => { - try { - let x509Certificate = socket.getPeerX509Certificate(); - resolve(x509Certificate); - } catch (error) { - reject(error); - } finally { - socket.end(); - } - } - ); - } catch (error) { - reject(error); - } - }); + const cert = await sslChecker(hostname); + return cert; + } catch (error) { + throw error; + } }; export { handleValidationError, handleError, fetchMonitorCertificate }; diff --git a/Server/controllers/inviteController.js b/Server/controllers/inviteController.js index 162861e89..72e3d99a3 100644 --- a/Server/controllers/inviteController.js +++ b/Server/controllers/inviteController.js @@ -1,7 +1,7 @@ import { - inviteRoleValidation, - inviteBodyValidation, - inviteVerificationBodyValidation, + inviteRoleValidation, + inviteBodyValidation, + inviteVerificationBodyValidation, } from "../validation/joi.js"; import logger from "../utils/logger.js"; import dotenv from "dotenv"; @@ -27,62 +27,58 @@ const SERVICE_NAME = "inviteController"; * @throws {Error} If there is an error during the process, especially if there is a validation error (422). */ const issueInvitation = async (req, res, next) => { - try { - // Only admins can invite - const token = getTokenFromHeaders(req.headers); - const { role, firstname, teamId } = jwt.decode(token); - req.body.teamId = teamId; - try { - await inviteRoleValidation.validateAsync({ roles: role }); - await inviteBodyValidation.validateAsync(req.body); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; - } + try { + // Only admins can invite + const token = getTokenFromHeaders(req.headers); + const { role, firstname, teamId } = jwt.decode(token); + req.body.teamId = teamId; + try { + await inviteRoleValidation.validateAsync({ roles: role }); + await inviteBodyValidation.validateAsync(req.body); + } catch (error) { + next(handleValidationError(error, SERVICE_NAME)); + return; + } - const inviteToken = await req.db.requestInviteToken({ ...req.body }); - const { clientHost } = req.settingsService.getSettings(); - req.emailService - .buildAndSendEmail( - "employeeActivationTemplate", - { - name: firstname, - link: `${clientHost}/register/${inviteToken.token}`, - }, - req.body.email, - "Welcome to Uptime Monitor" - ) - .catch((error) => { - logger.error("Error sending invite email", { - service: SERVICE_NAME, - error: error.message, - }); - }); + const inviteToken = await req.db.requestInviteToken({ ...req.body }); + const { clientHost } = req.settingsService.getSettings(); + req.emailService + .buildAndSendEmail( + "employeeActivationTemplate", + { + name: firstname, + link: `${clientHost}/register/${inviteToken.token}`, + }, + req.body.email, + "Welcome to Uptime Monitor" + ) + .catch((error) => { + logger.error("Error sending invite email", { + service: SERVICE_NAME, + error: error.message, + }); + }); - return res - .status(200) - .json({ success: true, msg: "Invite sent", data: inviteToken }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "inviteController")); - } + return res.status(200).json({ success: true, msg: "Invite sent", data: inviteToken }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "inviteController")); + } }; const inviteVerifyController = async (req, res, next) => { - try { - await inviteVerificationBodyValidation.validateAsync(req.body); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; - } + try { + await inviteVerificationBodyValidation.validateAsync(req.body); + } catch (error) { + next(handleValidationError(error, SERVICE_NAME)); + return; + } - try { - const invite = await req.db.getInviteToken(req.body.token); - res - .status(200) - .json({ status: "success", msg: "Invite verified", data: invite }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "inviteVerifyController")); - } + try { + const invite = await req.db.getInviteToken(req.body.token); + res.status(200).json({ status: "success", msg: "Invite verified", data: invite }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "inviteVerifyController")); + } }; export { issueInvitation, inviteVerifyController }; diff --git a/Server/controllers/maintenanceWindowController.js b/Server/controllers/maintenanceWindowController.js index 663429835..1700a483e 100644 --- a/Server/controllers/maintenanceWindowController.js +++ b/Server/controllers/maintenanceWindowController.js @@ -1,11 +1,11 @@ import { - createMaintenanceWindowBodyValidation, - editMaintenanceWindowByIdParamValidation, - editMaintenanceByIdWindowBodyValidation, - getMaintenanceWindowByIdParamValidation, - getMaintenanceWindowsByMonitorIdParamValidation, - getMaintenanceWindowsByTeamIdQueryValidation, - deleteMaintenanceWindowByIdParamValidation, + createMaintenanceWindowBodyValidation, + editMaintenanceWindowByIdParamValidation, + editMaintenanceByIdWindowBodyValidation, + getMaintenanceWindowByIdParamValidation, + getMaintenanceWindowsByMonitorIdParamValidation, + getMaintenanceWindowsByTeamIdQueryValidation, + deleteMaintenanceWindowByIdParamValidation, } from "../validation/joi.js"; import jwt from "jsonwebtoken"; import { getTokenFromHeaders } from "../utils/utils.js"; @@ -15,157 +15,153 @@ import { handleValidationError, handleError } from "./controllerUtils.js"; const SERVICE_NAME = "maintenanceWindowController"; const createMaintenanceWindows = async (req, res, next) => { - try { - await createMaintenanceWindowBodyValidation.validateAsync(req.body); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; - } - try { - const token = getTokenFromHeaders(req.headers); - const { jwtSecret } = req.settingsService.getSettings(); - const { teamId } = jwt.verify(token, jwtSecret); - const monitorIds = req.body.monitors; - const dbTransactions = monitorIds.map((monitorId) => { - return req.db.createMaintenanceWindow({ - teamId, - monitorId, - name: req.body.name, - active: req.body.active ? req.body.active : true, - repeat: req.body.repeat, - start: req.body.start, - end: req.body.end, - }); - }); - await Promise.all(dbTransactions); - return res.status(201).json({ - success: true, - msg: successMessages.MAINTENANCE_WINDOW_CREATE, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "createMaintenanceWindow")); - } + try { + await createMaintenanceWindowBodyValidation.validateAsync(req.body); + } catch (error) { + next(handleValidationError(error, SERVICE_NAME)); + return; + } + try { + const token = getTokenFromHeaders(req.headers); + const { jwtSecret } = req.settingsService.getSettings(); + const { teamId } = jwt.verify(token, jwtSecret); + const monitorIds = req.body.monitors; + const dbTransactions = monitorIds.map((monitorId) => { + return req.db.createMaintenanceWindow({ + teamId, + monitorId, + name: req.body.name, + active: req.body.active ? req.body.active : true, + repeat: req.body.repeat, + start: req.body.start, + end: req.body.end, + }); + }); + await Promise.all(dbTransactions); + return res.status(201).json({ + success: true, + msg: successMessages.MAINTENANCE_WINDOW_CREATE, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "createMaintenanceWindow")); + } }; const getMaintenanceWindowById = async (req, res, next) => { - try { - await getMaintenanceWindowByIdParamValidation.validateAsync(req.params); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; - } - try { - const maintenanceWindow = await req.db.getMaintenanceWindowById( - req.params.id - ); - return res.status(200).json({ - success: true, - msg: successMessages.MAINTENANCE_WINDOW_GET_BY_ID, - data: maintenanceWindow, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "getMaintenanceWindowById")); - } + try { + await getMaintenanceWindowByIdParamValidation.validateAsync(req.params); + } catch (error) { + next(handleValidationError(error, SERVICE_NAME)); + return; + } + try { + const maintenanceWindow = await req.db.getMaintenanceWindowById(req.params.id); + return res.status(200).json({ + success: true, + msg: successMessages.MAINTENANCE_WINDOW_GET_BY_ID, + data: maintenanceWindow, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "getMaintenanceWindowById")); + } }; const getMaintenanceWindowsByTeamId = async (req, res, next) => { - try { - await getMaintenanceWindowsByTeamIdQueryValidation.validateAsync(req.query); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; - } + try { + await getMaintenanceWindowsByTeamIdQueryValidation.validateAsync(req.query); + } catch (error) { + next(handleValidationError(error, SERVICE_NAME)); + return; + } - try { - const token = getTokenFromHeaders(req.headers); - const { jwtSecret } = req.settingsService.getSettings(); - const { teamId } = jwt.verify(token, jwtSecret); - const maintenanceWindows = await req.db.getMaintenanceWindowsByTeamId( - teamId, - req.query - ); + try { + const token = getTokenFromHeaders(req.headers); + const { jwtSecret } = req.settingsService.getSettings(); + const { teamId } = jwt.verify(token, jwtSecret); + const maintenanceWindows = await req.db.getMaintenanceWindowsByTeamId( + teamId, + req.query + ); - return res.status(200).json({ - success: true, - msg: successMessages.MAINTENANCE_WINDOW_GET_BY_TEAM, - data: maintenanceWindows, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "getMaintenanceWindowsByUserId")); - } + return res.status(200).json({ + success: true, + msg: successMessages.MAINTENANCE_WINDOW_GET_BY_TEAM, + data: maintenanceWindows, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "getMaintenanceWindowsByUserId")); + } }; const getMaintenanceWindowsByMonitorId = async (req, res, next) => { - try { - await getMaintenanceWindowsByMonitorIdParamValidation.validateAsync( - req.params - ); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; - } + try { + await getMaintenanceWindowsByMonitorIdParamValidation.validateAsync(req.params); + } catch (error) { + next(handleValidationError(error, SERVICE_NAME)); + return; + } - try { - const maintenanceWindows = await req.db.getMaintenanceWindowsByMonitorId( - req.params.monitorId - ); + try { + const maintenanceWindows = await req.db.getMaintenanceWindowsByMonitorId( + req.params.monitorId + ); - return res.status(200).json({ - success: true, - msg: successMessages.MAINTENANCE_WINDOW_GET_BY_USER, - data: maintenanceWindows, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "getMaintenanceWindowsByMonitorId")); - } + return res.status(200).json({ + success: true, + msg: successMessages.MAINTENANCE_WINDOW_GET_BY_USER, + data: maintenanceWindows, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "getMaintenanceWindowsByMonitorId")); + } }; const deleteMaintenanceWindow = async (req, res, next) => { - try { - await deleteMaintenanceWindowByIdParamValidation.validateAsync(req.params); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; - } - try { - await req.db.deleteMaintenanceWindowById(req.params.id); - return res.status(200).json({ - success: true, - msg: successMessages.MAINTENANCE_WINDOW_DELETE, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "deleteMaintenanceWindow")); - } + try { + await deleteMaintenanceWindowByIdParamValidation.validateAsync(req.params); + } catch (error) { + next(handleValidationError(error, SERVICE_NAME)); + return; + } + try { + await req.db.deleteMaintenanceWindowById(req.params.id); + return res.status(200).json({ + success: true, + msg: successMessages.MAINTENANCE_WINDOW_DELETE, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "deleteMaintenanceWindow")); + } }; const editMaintenanceWindow = async (req, res, next) => { - try { - await editMaintenanceWindowByIdParamValidation.validateAsync(req.params); - await editMaintenanceByIdWindowBodyValidation.validateAsync(req.body); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; - } - try { - const editedMaintenanceWindow = await req.db.editMaintenanceWindowById( - req.params.id, - req.body - ); - return res.status(200).json({ - success: true, - msg: successMessages.MAINTENANCE_WINDOW_EDIT, - data: editedMaintenanceWindow, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "editMaintenanceWindow")); - } + try { + await editMaintenanceWindowByIdParamValidation.validateAsync(req.params); + await editMaintenanceByIdWindowBodyValidation.validateAsync(req.body); + } catch (error) { + next(handleValidationError(error, SERVICE_NAME)); + return; + } + try { + const editedMaintenanceWindow = await req.db.editMaintenanceWindowById( + req.params.id, + req.body + ); + return res.status(200).json({ + success: true, + msg: successMessages.MAINTENANCE_WINDOW_EDIT, + data: editedMaintenanceWindow, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "editMaintenanceWindow")); + } }; export { - createMaintenanceWindows, - getMaintenanceWindowById, - getMaintenanceWindowsByTeamId, - getMaintenanceWindowsByMonitorId, - deleteMaintenanceWindow, - editMaintenanceWindow, + createMaintenanceWindows, + getMaintenanceWindowById, + getMaintenanceWindowsByTeamId, + getMaintenanceWindowsByMonitorId, + deleteMaintenanceWindow, + editMaintenanceWindow, }; diff --git a/Server/controllers/monitorController.js b/Server/controllers/monitorController.js index b675ab652..24761089f 100644 --- a/Server/controllers/monitorController.js +++ b/Server/controllers/monitorController.js @@ -3,6 +3,7 @@ import { getMonitorByIdQueryValidation, getMonitorsByTeamIdValidation, createMonitorBodyValidation, + getMonitorURLByQueryValidation, editMonitorBodyValidation, getMonitorsAndSummaryByTeamIdParamValidation, getMonitorsAndSummaryByTeamIdQueryValidation, @@ -12,18 +13,15 @@ import { getMonitorStatsByIdQueryValidation, getCertificateParamValidation, } from "../validation/joi.js"; -import * as tls from "tls"; - -const SERVICE_NAME = "monitorController"; +import sslChecker from "ssl-checker"; import { errorMessages, successMessages } from "../utils/messages.js"; import jwt from "jsonwebtoken"; import { getTokenFromHeaders } from "../utils/utils.js"; import logger from "../utils/logger.js"; -import { - handleError, - handleValidationError, - fetchMonitorCertificate, -} from "./controllerUtils.js"; +import { handleError, handleValidationError } from "./controllerUtils.js"; +import dns from "dns"; + +const SERVICE_NAME = "monitorController"; /** * Returns all monitors @@ -87,7 +85,7 @@ const getMonitorCertificate = async (req, res, next, fetchMonitorCertificate) => try { const { monitorId } = req.params; const monitor = await req.db.getMonitorById(monitorId); - const certificate = await fetchMonitorCertificate(tls, monitor); + const certificate = await fetchMonitorCertificate(sslChecker, monitor); if (certificate && certificate.validTo) { return res.status(200).json({ success: true, @@ -261,6 +259,44 @@ const createMonitor = async (req, res, next) => { } }; +/** + * Checks if the endpoint can be resolved + * @async + * @param {Object} req - The Express request object. + * @property {Object} req.query - The query parameters of the request. + * @param {Object} res - The Express response object. + * @param {function} next - The next middleware function. + * @returns {Object} The response object with a success status, a message, and the resolution result. + * @throws {Error} If there is an error during the process, especially if there is a validation error (422). + */ +const checkEndpointResolution = async (req, res, next) => { + try { + await getMonitorURLByQueryValidation.validateAsync(req.query); + } catch (error) { + next(handleValidationError(error, SERVICE_NAME)); + return; + } + + try { + let { monitorURL } = req.query; + monitorURL = new URL(monitorURL); + await new Promise((resolve, reject) => { + dns.resolve(monitorURL.hostname, (error) => { + if (error) { + reject(error); + } + resolve(); + }); + }); + return res.status(200).json({ + success: true, + msg: `URL resolved successfully`, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "checkEndpointResolution")); + } +} + /** * Deletes a monitor by its ID and also deletes associated checks, alerts, and notifications. * @async @@ -481,6 +517,7 @@ export { getMonitorsAndSummaryByTeamId, getMonitorsByTeamId, createMonitor, + checkEndpointResolution, deleteMonitor, deleteAllMonitors, editMonitor, diff --git a/Server/controllers/queueController.js b/Server/controllers/queueController.js index fbf7022d2..5e525de58 100644 --- a/Server/controllers/queueController.js +++ b/Server/controllers/queueController.js @@ -4,56 +4,54 @@ import { errorMessages, successMessages } from "../utils/messages.js"; const SERVICE_NAME = "JobQueueController"; const getMetrics = async (req, res, next) => { - try { - const metrics = await req.jobQueue.getMetrics(); - res.status(200).json({ - success: true, - msg: successMessages.QUEUE_GET_METRICS, - data: metrics, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "getMetrics")); - return; - } + try { + const metrics = await req.jobQueue.getMetrics(); + res.status(200).json({ + success: true, + msg: successMessages.QUEUE_GET_METRICS, + data: metrics, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "getMetrics")); + return; + } }; const getJobs = async (req, res, next) => { - try { - const jobs = await req.jobQueue.getJobStats(); - return res.status(200).json({ - success: true, - msg: successMessages.QUEUE_GET_METRICS, - data: jobs, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "getJobs")); - return; - } + try { + const jobs = await req.jobQueue.getJobStats(); + return res.status(200).json({ + success: true, + msg: successMessages.QUEUE_GET_METRICS, + data: jobs, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "getJobs")); + return; + } }; const addJob = async (req, res, next) => { - try { - await req.jobQueue.addJob(Math.random().toString(36).substring(7)); - return res.status(200).json({ - success: true, - msg: successMessages.QUEUE_ADD_JOB, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "addJob")); - return; - } + try { + await req.jobQueue.addJob(Math.random().toString(36).substring(7)); + return res.status(200).json({ + success: true, + msg: successMessages.QUEUE_ADD_JOB, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "addJob")); + return; + } }; const obliterateQueue = async (req, res, next) => { - try { - await req.jobQueue.obliterate(); - return res - .status(200) - .json({ success: true, msg: successMessages.QUEUE_OBLITERATE }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "obliterateQueue")); - return; - } + try { + await req.jobQueue.obliterate(); + return res.status(200).json({ success: true, msg: successMessages.QUEUE_OBLITERATE }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "obliterateQueue")); + return; + } }; export { getMetrics, getJobs, addJob, obliterateQueue }; diff --git a/Server/controllers/settingsController.js b/Server/controllers/settingsController.js index e96bedace..3e232ac7a 100644 --- a/Server/controllers/settingsController.js +++ b/Server/controllers/settingsController.js @@ -4,39 +4,39 @@ import { handleValidationError, handleError } from "./controllerUtils.js"; const SERVICE_NAME = "SettingsController"; const getAppSettings = async (req, res, next) => { - try { - const settings = { ...(await req.settingsService.getSettings()) }; - delete settings.jwtSecret; - return res.status(200).json({ - success: true, - msg: successMessages.GET_APP_SETTINGS, - data: settings, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "getAppSettings")); - } + try { + const settings = { ...(await req.settingsService.getSettings()) }; + delete settings.jwtSecret; + return res.status(200).json({ + success: true, + msg: successMessages.GET_APP_SETTINGS, + data: settings, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "getAppSettings")); + } }; const updateAppSettings = async (req, res, next) => { - try { - await updateAppSettingsBodyValidation.validateAsync(req.body); - } catch (error) { - next(handleValidationError(error, SERVICE_NAME)); - return; - } + try { + await updateAppSettingsBodyValidation.validateAsync(req.body); + } catch (error) { + next(handleValidationError(error, SERVICE_NAME)); + return; + } - try { - await req.db.updateAppSettings(req.body); - const updatedSettings = { ...(await req.settingsService.reloadSettings()) }; - delete updatedSettings.jwtSecret; - return res.status(200).json({ - success: true, - msg: successMessages.UPDATE_APP_SETTINGS, - data: updatedSettings, - }); - } catch (error) { - next(handleError(error, SERVICE_NAME, "updateAppSettings")); - } + try { + await req.db.updateAppSettings(req.body); + const updatedSettings = { ...(await req.settingsService.reloadSettings()) }; + delete updatedSettings.jwtSecret; + return res.status(200).json({ + success: true, + msg: successMessages.UPDATE_APP_SETTINGS, + data: updatedSettings, + }); + } catch (error) { + next(handleError(error, SERVICE_NAME, "updateAppSettings")); + } }; export { getAppSettings, updateAppSettings }; diff --git a/Server/db/FakeDb.js b/Server/db/FakeDb.js index a7c8bf4ee..4e9b32992 100644 --- a/Server/db/FakeDb.js +++ b/Server/db/FakeDb.js @@ -28,111 +28,111 @@ let FAKE_MONITOR_DATA = []; const USERS = []; const connect = async () => { - try { - await console.log("Connected to FakeDB"); - } catch (error) { - console.error(error); - } + try { + await console.log("Connected to FakeDB"); + } catch (error) { + console.error(error); + } }; const insertUser = async (req, res) => { - try { - const newUser = new UserModel({ ...req.body }); - const salt = await bcrypt.genSalt(10); //genSalt is asynchronous, need to wait - newUser.password = await bcrypt.hash(newUser.password, salt); // hash is also async, need to eitehr await or use hashSync - USERS.push(newUser); - const userToReturn = { ...newUser._doc }; - delete userToReturn.password; - return userToReturn; - } catch (error) { - throw error; - } + try { + const newUser = new UserModel({ ...req.body }); + const salt = await bcrypt.genSalt(10); //genSalt is asynchronous, need to wait + newUser.password = await bcrypt.hash(newUser.password, salt); // hash is also async, need to eitehr await or use hashSync + USERS.push(newUser); + const userToReturn = { ...newUser._doc }; + delete userToReturn.password; + return userToReturn; + } catch (error) { + throw error; + } }; const getUserByEmail = async (req, res) => { - const email = req.body.email; - try { - const idx = USERS.findIndex((user) => { - return user.email === email; - }); - if (idx === -1) { - return null; - } - return USERS[idx]; - } catch (error) { - throw new Error(`User with email ${email} not found`); - } + const email = req.body.email; + try { + const idx = USERS.findIndex((user) => { + return user.email === email; + }); + if (idx === -1) { + return null; + } + return USERS[idx]; + } catch (error) { + throw new Error(`User with email ${email} not found`); + } }; const getAllMonitors = async () => { - return FAKE_MONITOR_DATA; + return FAKE_MONITOR_DATA; }; const getMonitorById = async (monitorId) => { - const idx = FAKE_MONITOR_DATA.findIndex((monitor) => { - return monitor.id === monitorId; - }); - if (idx === -1) { - throw new Error(`Monitor with id ${monitorId} not found`); - } - return FAKE_MONITOR_DATA[idx]; + const idx = FAKE_MONITOR_DATA.findIndex((monitor) => { + return monitor.id === monitorId; + }); + if (idx === -1) { + throw new Error(`Monitor with id ${monitorId} not found`); + } + return FAKE_MONITOR_DATA[idx]; }; const getMonitorsByUserId = async (userId) => { - const userMonitors = FAKE_MONITOR_DATA.filter((monitor) => { - return monitor.userId === userId; - }); + const userMonitors = FAKE_MONITOR_DATA.filter((monitor) => { + return monitor.userId === userId; + }); - if (userMonitors.length === 0) { - throw new Error(`Monitors for user ${userId} not found`); - } - return userMonitors; + if (userMonitors.length === 0) { + throw new Error(`Monitors for user ${userId} not found`); + } + return userMonitors; }; const createMonitor = async (req, res) => { - const monitor = new Monitor(req.body); - monitor.createdAt = Date.now(); - monitor.updatedAt = Date.now(); - FAKE_MONITOR_DATA.push(monitor); - return monitor; + const monitor = new Monitor(req.body); + monitor.createdAt = Date.now(); + monitor.updatedAt = Date.now(); + FAKE_MONITOR_DATA.push(monitor); + return monitor; }; const deleteMonitor = async (req, res) => { - const monitorId = req.params.monitorId; - try { - const monitor = getMonitorById(monitorId); - FAKE_MONITOR_DATA = FAKE_MONITOR_DATA.filter((monitor) => { - return monitor.id !== monitorId; - }); - return monitor; - } catch (error) { - throw error; - } + const monitorId = req.params.monitorId; + try { + const monitor = getMonitorById(monitorId); + FAKE_MONITOR_DATA = FAKE_MONITOR_DATA.filter((monitor) => { + return monitor.id !== monitorId; + }); + return monitor; + } catch (error) { + throw error; + } }; const editMonitor = async (req, res) => { - const monitorId = req.params.monitorId; - const idx = FAKE_MONITOR_DATA.findIndex((monitor) => { - return monitor._id.toString() === monitorId; - }); - const oldMonitor = FAKE_MONITOR_DATA[idx]; - const editedMonitor = new Monitor({ ...req.body }); - editedMonitor._id = oldMonitor._id; - editedMonitor.userId = oldMonitor.userId; - editedMonitor.updatedAt = Date.now(); - editedMonitor.createdAt = oldMonitor.createdAt; - FAKE_MONITOR_DATA[idx] = editedMonitor; - return FAKE_MONITOR_DATA[idx]; + const monitorId = req.params.monitorId; + const idx = FAKE_MONITOR_DATA.findIndex((monitor) => { + return monitor._id.toString() === monitorId; + }); + const oldMonitor = FAKE_MONITOR_DATA[idx]; + const editedMonitor = new Monitor({ ...req.body }); + editedMonitor._id = oldMonitor._id; + editedMonitor.userId = oldMonitor.userId; + editedMonitor.updatedAt = Date.now(); + editedMonitor.createdAt = oldMonitor.createdAt; + FAKE_MONITOR_DATA[idx] = editedMonitor; + return FAKE_MONITOR_DATA[idx]; }; module.exports = { - connect, - insertUser, - getUserByEmail, - getAllMonitors, - getMonitorById, - getMonitorsByUserId, - createMonitor, - deleteMonitor, - editMonitor, + connect, + insertUser, + getUserByEmail, + getAllMonitors, + getMonitorById, + getMonitorsByUserId, + createMonitor, + deleteMonitor, + editMonitor, }; diff --git a/Server/db/models/AppSettings.js b/Server/db/models/AppSettings.js index 547af5f78..c4faa0881 100644 --- a/Server/db/models/AppSettings.js +++ b/Server/db/models/AppSettings.js @@ -1,91 +1,91 @@ import mongoose from "mongoose"; const AppSettingsSchema = mongoose.Schema( - { - apiBaseUrl: { - type: String, - required: true, - default: "http://localhost:5000/api/v1", - }, - logLevel: { - type: String, - default: "debug", - enum: ["debug", "none", "error", "warn"], - }, - clientHost: { - type: String, - required: true, - default: "http://localhost:5173", - }, - jwtSecret: { - type: String, - required: true, - default: "my_secret", - }, - refreshTokenSecret: { - type: String, - required: true, - default: "my_refresh_secret", - }, - dbType: { - type: String, - required: true, - default: "MongoDB", - }, - dbConnectionString: { - type: String, - required: true, - default: "mongodb://localhost:27017/uptime_db", - }, - redisHost: { - type: String, - required: true, - default: "127.0.0.1", - }, - redisPort: { - type: Number, - default: "6379", - }, - jwtTTL: { - type: String, - required: true, - default: "2h", - }, - refreshTokenTTL: { - type: String, - required: true, - default: "7d", - }, - pagespeedApiKey: { - type: String, - default: "", - }, - systemEmailHost: { - type: String, - default: "smtp.gmail.com", - }, - systemEmailPort: { - type: Number, - default: 465, - }, - systemEmailAddress: { - type: String, - default: "", - }, - systemEmailPassword: { - type: String, - default: "", - }, - singleton: { - type: Boolean, - required: true, - unique: true, - default: true, - }, - }, - { - timestamps: true, - } + { + apiBaseUrl: { + type: String, + required: true, + default: "http://localhost:5000/api/v1", + }, + logLevel: { + type: String, + default: "debug", + enum: ["debug", "none", "error", "warn"], + }, + clientHost: { + type: String, + required: true, + default: "http://localhost:5173", + }, + jwtSecret: { + type: String, + required: true, + default: "my_secret", + }, + refreshTokenSecret: { + type: String, + required: true, + default: "my_refresh_secret", + }, + dbType: { + type: String, + required: true, + default: "MongoDB", + }, + dbConnectionString: { + type: String, + required: true, + default: "mongodb://localhost:27017/uptime_db", + }, + redisHost: { + type: String, + required: true, + default: "127.0.0.1", + }, + redisPort: { + type: Number, + default: "6379", + }, + jwtTTL: { + type: String, + required: true, + default: "2h", + }, + refreshTokenTTL: { + type: String, + required: true, + default: "7d", + }, + pagespeedApiKey: { + type: String, + default: "", + }, + systemEmailHost: { + type: String, + default: "smtp.gmail.com", + }, + systemEmailPort: { + type: Number, + default: 465, + }, + systemEmailAddress: { + type: String, + default: "", + }, + systemEmailPassword: { + type: String, + default: "", + }, + singleton: { + type: Boolean, + required: true, + unique: true, + default: true, + }, + }, + { + timestamps: true, + } ); export default mongoose.model("AppSettings", AppSettingsSchema); diff --git a/Server/db/models/Check.js b/Server/db/models/Check.js index e560f8530..320ddefe9 100644 --- a/Server/db/models/Check.js +++ b/Server/db/models/Check.js @@ -9,67 +9,67 @@ import Notification from "./Notification.js"; * about the status and response of a particular check event. */ const CheckSchema = mongoose.Schema( - { - /** - * Reference to the associated Monitor document. - * - * @type {mongoose.Schema.Types.ObjectId} - */ - monitorId: { - type: mongoose.Schema.Types.ObjectId, - ref: "Monitor", - immutable: true, - index: true, - }, - /** - * Status of the check (true for up, false for down). - * - * @type {Boolean} - */ - status: { - type: Boolean, - index: true, - }, - /** - * Response time of the check in milliseconds. - * - * @type {Number} - */ - responseTime: { - type: Number, - }, - /** - * HTTP status code received during the check. - * - * @type {Number} - */ - statusCode: { - type: Number, - index: true, - }, - /** - * Message or description of the check result. - * - * @type {String} - */ - message: { - type: String, - }, - /** - * Expiry date of the check, auto-calculated to expire after 30 days. - * - * @type {Date} - */ + { + /** + * Reference to the associated Monitor document. + * + * @type {mongoose.Schema.Types.ObjectId} + */ + monitorId: { + type: mongoose.Schema.Types.ObjectId, + ref: "Monitor", + immutable: true, + index: true, + }, + /** + * Status of the check (true for up, false for down). + * + * @type {Boolean} + */ + status: { + type: Boolean, + index: true, + }, + /** + * Response time of the check in milliseconds. + * + * @type {Number} + */ + responseTime: { + type: Number, + }, + /** + * HTTP status code received during the check. + * + * @type {Number} + */ + statusCode: { + type: Number, + index: true, + }, + /** + * Message or description of the check result. + * + * @type {String} + */ + message: { + type: String, + }, + /** + * Expiry date of the check, auto-calculated to expire after 30 days. + * + * @type {Date} + */ - expiry: { - type: Date, - default: Date.now, - expires: 60 * 60 * 24 * 30, // 30 days - }, - }, - { - timestamps: true, // Adds createdAt and updatedAt timestamps - } + expiry: { + type: Date, + default: Date.now, + expires: 60 * 60 * 24 * 30, // 30 days + }, + }, + { + timestamps: true, // Adds createdAt and updatedAt timestamps + } ); CheckSchema.index({ createdAt: 1 }); diff --git a/Server/db/models/HardwareCheck.js b/Server/db/models/HardwareCheck.js new file mode 100644 index 000000000..b710087bf --- /dev/null +++ b/Server/db/models/HardwareCheck.js @@ -0,0 +1,60 @@ +import mongoose from "mongoose"; + +const cpuSchema = mongoose.Schema({ + physical_core: { type: Number, default: 0 }, + logical_core: { type: Number, default: 0 }, + frequency: { type: Number, default: 0 }, + temperature: { type: Number, default: 0 }, + free_percent: { type: Number, default: 0 }, + usage_percent: { type: Number, default: 0 }, +}); + +const memorySchema = mongoose.Schema({ + total_bytes: { type: Number, default: 0 }, + available_bytes: { type: Number, default: 0 }, + used_bytes: { type: Number, default: 0 }, + usage_percent: { type: Number, default: 0 }, +}); + +const discSchema = mongoose.Schema({ + read_speed_bytes: { type: Number, default: 0 }, + write_speed_bytes: { type: Number, default: 0 }, + total_bytes: { type: Number, default: 0 }, + free_bytes: { type: Number, default: 0 }, + usage_percent: { type: Number, default: 0 }, +}); + +const hostSchema = mongoose.Schema({ + os: { type: String, default: "" }, + platform: { type: String, default: "" }, + kernel_version: { type: String, default: "" }, +}); + +const HardwareCheckSchema = mongoose.Schema( + { + monitorId: { + type: mongoose.Schema.Types.ObjectId, + ref: "Monitor", + immutable: true, + }, + cpu: { + type: cpuSchema, + default: () => ({}), + }, + memory: { + type: memorySchema, + default: () => ({}), + }, + disk: { + type: [discSchema], + default: () => [], + }, + host: { + type: hostSchema, + default: () => ({}), + }, + }, + { timestamps: true } +); + +export default mongoose.model("HardwareCheck", HardwareCheckSchema); diff --git a/Server/db/models/InviteToken.js b/Server/db/models/InviteToken.js index 5e12d8e5d..0c2402b07 100644 --- a/Server/db/models/InviteToken.js +++ b/Server/db/models/InviteToken.js @@ -1,34 +1,34 @@ import mongoose from "mongoose"; const InviteTokenSchema = mongoose.Schema( - { - email: { - type: String, - required: true, - unique: true, - }, - teamId: { - type: mongoose.Schema.Types.ObjectId, - ref: "Team", - immutable: true, - required: true, - }, - role: { - type: Array, - required: true, - }, - token: { - type: String, - required: true, - }, - expiry: { - type: Date, - default: Date.now, - expires: 3600, - }, - }, - { - timestamps: true, - } + { + email: { + type: String, + required: true, + unique: true, + }, + teamId: { + type: mongoose.Schema.Types.ObjectId, + ref: "Team", + immutable: true, + required: true, + }, + role: { + type: Array, + required: true, + }, + token: { + type: String, + required: true, + }, + expiry: { + type: Date, + default: Date.now, + expires: 3600, + }, + }, + { + timestamps: true, + } ); export default mongoose.model("InviteToken", InviteTokenSchema); diff --git a/Server/db/models/MaintenanceWindow.js b/Server/db/models/MaintenanceWindow.js index 95664bdcd..b68abb413 100644 --- a/Server/db/models/MaintenanceWindow.js +++ b/Server/db/models/MaintenanceWindow.js @@ -27,42 +27,42 @@ import mongoose from "mongoose"; */ const MaintenanceWindow = mongoose.Schema( - { - monitorId: { - type: mongoose.Schema.Types.ObjectId, - ref: "Monitor", - immutable: true, - }, - teamId: { - type: mongoose.Schema.Types.ObjectId, - ref: "Team", - immutable: true, - }, - active: { - type: Boolean, - default: true, - }, - name: { - type: String, - }, - repeat: { - type: Number, - }, - start: { - type: Date, - }, - end: { - type: Date, - }, - expiry: { - type: Date, - index: { expires: "0s" }, - }, - }, + { + monitorId: { + type: mongoose.Schema.Types.ObjectId, + ref: "Monitor", + immutable: true, + }, + teamId: { + type: mongoose.Schema.Types.ObjectId, + ref: "Team", + immutable: true, + }, + active: { + type: Boolean, + default: true, + }, + name: { + type: String, + }, + repeat: { + type: Number, + }, + start: { + type: Date, + }, + end: { + type: Date, + }, + expiry: { + type: Date, + index: { expires: "0s" }, + }, + }, - { - timestamps: true, - } + { + timestamps: true, + } ); export default mongoose.model("MaintenanceWindow", MaintenanceWindow); diff --git a/Server/db/models/Monitor.js b/Server/db/models/Monitor.js index 2184484aa..6d718e9cd 100644 --- a/Server/db/models/Monitor.js +++ b/Server/db/models/Monitor.js @@ -2,62 +2,62 @@ import mongoose from "mongoose"; import Notification from "./Notification.js"; const MonitorSchema = mongoose.Schema( - { - userId: { - type: mongoose.Schema.Types.ObjectId, - ref: "User", - immutable: true, - required: true, - }, - teamId: { - type: mongoose.Schema.Types.ObjectId, - ref: "Team", - immutable: true, - required: true, - }, - name: { - type: String, - required: true, - }, - description: { - type: String, - }, - status: { - type: Boolean, - default: undefined, - }, - type: { - type: String, - required: true, - enum: ["http", "ping", "pagespeed"], - }, - url: { - type: String, - required: true, - }, - isActive: { - type: Boolean, - default: true, - }, - interval: { - // in milliseconds - type: Number, - default: 60000, - }, - uptimePercentage: { - type: Number, - default: undefined, - }, - notifications: [ - { - type: mongoose.Schema.Types.ObjectId, - ref: "Notification", - }, - ], - }, - { - timestamps: true, - } + { + userId: { + type: mongoose.Schema.Types.ObjectId, + ref: "User", + immutable: true, + required: true, + }, + teamId: { + type: mongoose.Schema.Types.ObjectId, + ref: "Team", + immutable: true, + required: true, + }, + name: { + type: String, + required: true, + }, + description: { + type: String, + }, + status: { + type: Boolean, + default: undefined, + }, + type: { + type: String, + required: true, + enum: ["http", "ping", "pagespeed", "hardware"], + }, + url: { + type: String, + required: true, + }, + isActive: { + type: Boolean, + default: true, + }, + interval: { + // in milliseconds + type: Number, + default: 60000, + }, + uptimePercentage: { + type: Number, + default: undefined, + }, + notifications: [ + { + type: mongoose.Schema.Types.ObjectId, + ref: "Notification", + }, + ], + }, + { + timestamps: true, + } ); export default mongoose.model("Monitor", MonitorSchema); diff --git a/Server/db/models/Notification.js b/Server/db/models/Notification.js index 79217f6fe..9edb0187a 100644 --- a/Server/db/models/Notification.js +++ b/Server/db/models/Notification.js @@ -1,24 +1,24 @@ import mongoose from "mongoose"; const NotificationSchema = mongoose.Schema( - { - monitorId: { - type: mongoose.Schema.Types.ObjectId, - ref: "Monitor", - immutable: true, - }, - type: { - type: String, - enum: ["email", "sms"], - }, - address: { - type: String, - }, - phone: { - type: String, - }, - }, - { - timestamps: true, - } + { + monitorId: { + type: mongoose.Schema.Types.ObjectId, + ref: "Monitor", + immutable: true, + }, + type: { + type: String, + enum: ["email", "sms"], + }, + address: { + type: String, + }, + phone: { + type: String, + }, + }, + { + timestamps: true, + } ); export default mongoose.model("Notification", NotificationSchema); diff --git a/Server/db/models/PageSpeedCheck.js b/Server/db/models/PageSpeedCheck.js index c3afd5231..a7c013f93 100644 --- a/Server/db/models/PageSpeedCheck.js +++ b/Server/db/models/PageSpeedCheck.js @@ -1,36 +1,36 @@ import mongoose from "mongoose"; const AuditSchema = mongoose.Schema({ - id: { type: String, required: true }, - title: { type: String, required: true }, - description: { type: String, required: true }, - score: { type: Number, required: true }, - scoreDisplayMode: { type: String, required: true }, - displayValue: { type: String, required: true }, - numericValue: { type: Number, required: true }, - numericUnit: { type: String, required: true }, + id: { type: String, required: true }, + title: { type: String, required: true }, + description: { type: String, required: true }, + score: { type: Number, required: true }, + scoreDisplayMode: { type: String, required: true }, + displayValue: { type: String, required: true }, + numericValue: { type: Number, required: true }, + numericUnit: { type: String, required: true }, }); const AuditsSchema = mongoose.Schema({ - cls: { - type: AuditSchema, - required: true, - }, - si: { - type: AuditSchema, - required: true, - }, - fcp: { - type: AuditSchema, - required: true, - }, - lcp: { - type: AuditSchema, - required: true, - }, - tbt: { - type: AuditSchema, - required: true, - }, + cls: { + type: AuditSchema, + required: true, + }, + si: { + type: AuditSchema, + required: true, + }, + fcp: { + type: AuditSchema, + required: true, + }, + lcp: { + type: AuditSchema, + required: true, + }, + tbt: { + type: AuditSchema, + required: true, + }, }); /** @@ -44,40 +44,40 @@ const AuditsSchema = mongoose.Schema({ */ const PageSpeedCheck = mongoose.Schema( - { - monitorId: { - type: mongoose.Schema.Types.ObjectId, - ref: "Monitor", - immutable: true, - }, - status: { - type: Boolean, - required: true, - }, - accessibility: { - type: Number, - required: true, - }, - bestPractices: { - type: Number, - required: true, - }, - seo: { - type: Number, - required: true, - }, - performance: { - type: Number, - required: true, - }, - audits: { - type: AuditsSchema, - required: true, - }, - }, - { - timestamps: true, - } + { + monitorId: { + type: mongoose.Schema.Types.ObjectId, + ref: "Monitor", + immutable: true, + }, + status: { + type: Boolean, + required: true, + }, + accessibility: { + type: Number, + required: true, + }, + bestPractices: { + type: Number, + required: true, + }, + seo: { + type: Number, + required: true, + }, + performance: { + type: Number, + required: true, + }, + audits: { + type: AuditsSchema, + required: true, + }, + }, + { + timestamps: true, + } ); /** @@ -86,26 +86,26 @@ const PageSpeedCheck = mongoose.Schema( */ PageSpeedCheck.pre("save", async function (next) { - try { - const monitor = await mongoose.model("Monitor").findById(this.monitorId); - if (monitor && monitor.status !== this.status) { - if (monitor.status === true && this.status === false) { - // TODO issue alert - console.log("Monitor went down"); - } + try { + const monitor = await mongoose.model("Monitor").findById(this.monitorId); + if (monitor && monitor.status !== this.status) { + if (monitor.status === true && this.status === false) { + // TODO issue alert + console.log("Monitor went down"); + } - if (monitor.status === false && this.status === true) { - // TODO issue alert - console.log("Monitor went up"); - } - monitor.status = this.status; - await monitor.save(); - } - } catch (error) { - console.log(error); - } finally { - next(); - } + if (monitor.status === false && this.status === true) { + // TODO issue alert + console.log("Monitor went up"); + } + monitor.status = this.status; + await monitor.save(); + } + } catch (error) { + console.log(error); + } finally { + next(); + } }); export default mongoose.model("PageSpeedCheck", PageSpeedCheck); diff --git a/Server/db/models/RecoveryToken.js b/Server/db/models/RecoveryToken.js index ed7eee3b1..2219a4bca 100644 --- a/Server/db/models/RecoveryToken.js +++ b/Server/db/models/RecoveryToken.js @@ -1,25 +1,25 @@ import mongoose from "mongoose"; const RecoveryTokenSchema = mongoose.Schema( - { - email: { - type: String, - required: true, - unique: true, - }, - token: { - type: String, - required: true, - }, - expiry: { - type: Date, - default: Date.now, - expires: 600, - }, - }, - { - timestamps: true, - } + { + email: { + type: String, + required: true, + unique: true, + }, + token: { + type: String, + required: true, + }, + expiry: { + type: Date, + default: Date.now, + expires: 600, + }, + }, + { + timestamps: true, + } ); export default mongoose.model("RecoveryToken", RecoveryTokenSchema); diff --git a/Server/db/models/Team.js b/Server/db/models/Team.js index c125df329..95337c201 100644 --- a/Server/db/models/Team.js +++ b/Server/db/models/Team.js @@ -1,14 +1,14 @@ import mongoose from "mongoose"; const TeamSchema = mongoose.Schema( - { - email: { - type: String, - required: true, - unique: true, - }, - }, - { - timestamps: true, - } + { + email: { + type: String, + required: true, + unique: true, + }, + }, + { + timestamps: true, + } ); export default mongoose.model("Team", TeamSchema); diff --git a/Server/db/mongo/MongoDB.js b/Server/db/mongo/MongoDB.js index ad9ea2a2a..9031a741f 100644 --- a/Server/db/mongo/MongoDB.js +++ b/Server/db/mongo/MongoDB.js @@ -7,34 +7,34 @@ import AppSettings from "../models/AppSettings.js"; //**************************************** const connect = async () => { - try { - const connectionString = - process.env.DB_CONNECTION_STRING || "mongodb://localhost:27017/uptime_db"; - await mongoose.connect(connectionString); - // If there are no AppSettings, create one - let appSettings = await AppSettings.find(); - if (appSettings.length === 0) { - appSettings = new AppSettings({}); - await appSettings.save(); - } + try { + const connectionString = + process.env.DB_CONNECTION_STRING || "mongodb://localhost:27017/uptime_db"; + await mongoose.connect(connectionString); + // If there are no AppSettings, create one + let appSettings = await AppSettings.find(); + if (appSettings.length === 0) { + appSettings = new AppSettings({}); + await appSettings.save(); + } - console.log("Connected to MongoDB"); - } catch (error) { - console.error("Failed to connect to MongoDB"); - throw error; - } + console.log("Connected to MongoDB"); + } catch (error) { + console.error("Failed to connect to MongoDB"); + throw error; + } }; const checkSuperadmin = async (req, res) => { - try { - const superAdmin = await UserModel.findOne({ role: "superadmin" }); - if (superAdmin !== null) { - return true; - } - return false; - } catch (error) { - throw error; - } + try { + const superAdmin = await UserModel.findOne({ role: "superadmin" }); + if (superAdmin !== null) { + return true; + } + return false; + } catch (error) { + throw error; + } }; //**************************************** @@ -42,14 +42,14 @@ const checkSuperadmin = async (req, res) => { //**************************************** import { - insertUser, - getUserByEmail, - updateUser, - deleteUser, - deleteTeam, - deleteAllOtherUsers, - getAllUsers, - logoutUser, + insertUser, + getUserByEmail, + updateUser, + deleteUser, + deleteTeam, + deleteAllOtherUsers, + getAllUsers, + logoutUser, } from "./modules/userModule.js"; //**************************************** @@ -57,18 +57,18 @@ import { //**************************************** import { - requestInviteToken, - getInviteToken, - getInviteTokenAndDelete, + requestInviteToken, + getInviteToken, + getInviteTokenAndDelete, } from "./modules/inviteModule.js"; //**************************************** // Recovery Operations //**************************************** import { - requestRecoveryToken, - validateRecoveryToken, - resetPassword, + requestRecoveryToken, + validateRecoveryToken, + resetPassword, } from "./modules/recoveryModule.js"; //**************************************** @@ -76,17 +76,17 @@ import { //**************************************** import { - getAllMonitors, - getMonitorStatsById, - getMonitorById, - getMonitorsAndSummaryByTeamId, - getMonitorsByTeamId, - createMonitor, - deleteMonitor, - deleteAllMonitors, - deleteMonitorsByUserId, - editMonitor, - addDemoMonitors, + getAllMonitors, + getMonitorStatsById, + getMonitorById, + getMonitorsAndSummaryByTeamId, + getMonitorsByTeamId, + createMonitor, + deleteMonitor, + deleteAllMonitors, + deleteMonitorsByUserId, + editMonitor, + addDemoMonitors, } from "./modules/monitorModule.js"; //**************************************** @@ -94,46 +94,51 @@ import { //**************************************** import { - createPageSpeedCheck, - getPageSpeedChecks, - deletePageSpeedChecksByMonitorId, + createPageSpeedCheck, + getPageSpeedChecks, + deletePageSpeedChecksByMonitorId, } from "./modules/pageSpeedCheckModule.js"; +//**************************************** +// Hardware Checks +//**************************************** +import { createHardwareCheck } from "./modules/hardwareCheckModule.js"; + //**************************************** // Checks //**************************************** import { - createCheck, - getChecksCount, - getChecks, - getTeamChecks, - deleteChecks, - deleteChecksByTeamId, - updateChecksTTL, + createCheck, + getChecksCount, + getChecks, + getTeamChecks, + deleteChecks, + deleteChecksByTeamId, + updateChecksTTL, } from "./modules/checkModule.js"; //**************************************** // Maintenance Window //**************************************** import { - createMaintenanceWindow, - getMaintenanceWindowById, - getMaintenanceWindowsByTeamId, - getMaintenanceWindowsByMonitorId, - deleteMaintenanceWindowById, - deleteMaintenanceWindowByMonitorId, - deleteMaintenanceWindowByUserId, - editMaintenanceWindowById, + createMaintenanceWindow, + getMaintenanceWindowById, + getMaintenanceWindowsByTeamId, + getMaintenanceWindowsByMonitorId, + deleteMaintenanceWindowById, + deleteMaintenanceWindowByMonitorId, + deleteMaintenanceWindowByUserId, + editMaintenanceWindowById, } from "./modules/maintenanceWindowModule.js"; //**************************************** // Notifications //**************************************** import { - createNotification, - getNotificationsByMonitorId, - deleteNotificationsByMonitorId, + createNotification, + getNotificationsByMonitorId, + deleteNotificationsByMonitorId, } from "./modules/notificationModule.js"; //**************************************** @@ -142,54 +147,55 @@ import { import { getAppSettings, updateAppSettings } from "./modules/settingsModule.js"; export default { - connect, - insertUser, - getUserByEmail, - updateUser, - deleteUser, - deleteTeam, - deleteAllOtherUsers, - getAllUsers, - logoutUser, - requestInviteToken, - getInviteToken, - getInviteTokenAndDelete, - requestRecoveryToken, - validateRecoveryToken, - resetPassword, - checkSuperadmin, - getAllMonitors, - getMonitorStatsById, - getMonitorById, - getMonitorsAndSummaryByTeamId, - getMonitorsByTeamId, - createMonitor, - deleteMonitor, - deleteAllMonitors, - editMonitor, - addDemoMonitors, - createCheck, - getChecksCount, - getChecks, - getTeamChecks, - deleteChecks, - deleteChecksByTeamId, - updateChecksTTL, - deleteMonitorsByUserId, - createPageSpeedCheck, - getPageSpeedChecks, - deletePageSpeedChecksByMonitorId, - createMaintenanceWindow, - getMaintenanceWindowsByTeamId, - getMaintenanceWindowById, - getMaintenanceWindowsByMonitorId, - deleteMaintenanceWindowById, - deleteMaintenanceWindowByMonitorId, - deleteMaintenanceWindowByUserId, - editMaintenanceWindowById, - createNotification, - getNotificationsByMonitorId, - deleteNotificationsByMonitorId, - getAppSettings, - updateAppSettings, + connect, + insertUser, + getUserByEmail, + updateUser, + deleteUser, + deleteTeam, + deleteAllOtherUsers, + getAllUsers, + logoutUser, + requestInviteToken, + getInviteToken, + getInviteTokenAndDelete, + requestRecoveryToken, + validateRecoveryToken, + resetPassword, + checkSuperadmin, + getAllMonitors, + getMonitorStatsById, + getMonitorById, + getMonitorsAndSummaryByTeamId, + getMonitorsByTeamId, + createMonitor, + deleteMonitor, + deleteAllMonitors, + editMonitor, + addDemoMonitors, + createCheck, + getChecksCount, + getChecks, + getTeamChecks, + deleteChecks, + deleteChecksByTeamId, + updateChecksTTL, + deleteMonitorsByUserId, + createPageSpeedCheck, + getPageSpeedChecks, + deletePageSpeedChecksByMonitorId, + createHardwareCheck, + createMaintenanceWindow, + getMaintenanceWindowsByTeamId, + getMaintenanceWindowById, + getMaintenanceWindowsByMonitorId, + deleteMaintenanceWindowById, + deleteMaintenanceWindowByMonitorId, + deleteMaintenanceWindowByUserId, + editMaintenanceWindowById, + createNotification, + getNotificationsByMonitorId, + deleteNotificationsByMonitorId, + getAppSettings, + updateAppSettings, }; diff --git a/Server/db/mongo/modules/checkModule.js b/Server/db/mongo/modules/checkModule.js index ae1d5df4d..c01dceb07 100644 --- a/Server/db/mongo/modules/checkModule.js +++ b/Server/db/mongo/modules/checkModule.js @@ -4,9 +4,9 @@ import User from "../../models/User.js"; import logger from "../../../utils/logger.js"; const SERVICE_NAME = "checkModule"; const dateRangeLookup = { - day: new Date(new Date().setDate(new Date().getDate() - 1)), - week: new Date(new Date().setDate(new Date().getDate() - 7)), - month: new Date(new Date().setMonth(new Date().getMonth() - 1)), + day: new Date(new Date().setDate(new Date().getDate() - 1)), + week: new Date(new Date().setDate(new Date().getDate() - 7)), + month: new Date(new Date().setMonth(new Date().getMonth() - 1)), }; /** @@ -23,67 +23,67 @@ const dateRangeLookup = { */ const createCheck = async (checkData) => { - try { - const { monitorId, status } = checkData; - const n = (await Check.countDocuments({ monitorId })) + 1; - const check = await new Check({ ...checkData }).save(); - const monitor = await Monitor.findById(monitorId); + try { + const { monitorId, status } = checkData; + const n = (await Check.countDocuments({ monitorId })) + 1; + const check = await new Check({ ...checkData }).save(); + const monitor = await Monitor.findById(monitorId); - if (!monitor) { - logger.error("Monitor not found", { - service: SERVICE_NAME, - monitorId, - }); - return; - } + if (!monitor) { + logger.error("Monitor not found", { + service: SERVICE_NAME, + monitorId, + }); + return; + } - // Update uptime percentage - if (monitor.uptimePercentage === undefined) { - monitor.uptimePercentage = status === true ? 1 : 0; - } else { - monitor.uptimePercentage = - (monitor.uptimePercentage * (n - 1) + (status === true ? 1 : 0)) / n; - } + // Update uptime percentage + if (monitor.uptimePercentage === undefined) { + monitor.uptimePercentage = status === true ? 1 : 0; + } else { + monitor.uptimePercentage = + (monitor.uptimePercentage * (n - 1) + (status === true ? 1 : 0)) / n; + } - await monitor.save(); + await monitor.save(); - return check; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "createCheck"; - throw error; - } + return check; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "createCheck"; + throw error; + } }; const getChecksCount = async (req) => { - const monitorId = req.params.monitorId; - const dateRange = req.query.dateRange; - const filter = req.query.filter; - // Build query - const checksQuery = { monitorId: monitorId }; - // Filter checks by "day", "week", or "month" - if (dateRange !== undefined) { - checksQuery.createdAt = { $gte: dateRangeLookup[dateRange] }; - } + const monitorId = req.params.monitorId; + const dateRange = req.query.dateRange; + const filter = req.query.filter; + // Build query + const checksQuery = { monitorId: monitorId }; + // Filter checks by "day", "week", or "month" + if (dateRange !== undefined) { + checksQuery.createdAt = { $gte: dateRangeLookup[dateRange] }; + } - if (filter !== undefined) { - checksQuery.status = false; - switch (filter) { - case "all": - break; - case "down": - break; - case "resolve": - checksQuery.statusCode = 5000; - break; - default: - console.log("default"); - break; - } - } + if (filter !== undefined) { + checksQuery.status = false; + switch (filter) { + case "all": + break; + case "down": + break; + case "resolve": + checksQuery.statusCode = 5000; + break; + default: + console.log("default"); + break; + } + } - const count = await Check.countDocuments(checksQuery); - return count; + const count = await Check.countDocuments(checksQuery); + return count; }; /** @@ -95,104 +95,104 @@ const getChecksCount = async (req) => { */ const getChecks = async (req) => { - try { - const { monitorId } = req.params; - let { sortOrder, limit, dateRange, filter, page, rowsPerPage } = req.query; - // Default limit to 0 if not provided - limit = limit === "undefined" ? 0 : limit; + try { + const { monitorId } = req.params; + let { sortOrder, limit, dateRange, filter, page, rowsPerPage } = req.query; + // Default limit to 0 if not provided + limit = limit === "undefined" ? 0 : limit; - // Default sort order is newest -> oldest - sortOrder = sortOrder === "asc" ? 1 : -1; + // Default sort order is newest -> oldest + sortOrder = sortOrder === "asc" ? 1 : -1; - // Build query - const checksQuery = { monitorId: monitorId }; - // Filter checks by "day", "week", or "month" - if (dateRange !== undefined) { - checksQuery.createdAt = { $gte: dateRangeLookup[dateRange] }; - } - // Fitler checks by status - if (filter !== undefined) { - checksQuery.status = false; - switch (filter) { - case "all": - break; - case "down": - break; - case "resolve": - checksQuery.statusCode = 5000; - break; - default: - console.log("default"); - break; - } - } + // Build query + const checksQuery = { monitorId: monitorId }; + // Filter checks by "day", "week", or "month" + if (dateRange !== undefined) { + checksQuery.createdAt = { $gte: dateRangeLookup[dateRange] }; + } + // Fitler checks by status + if (filter !== undefined) { + checksQuery.status = false; + switch (filter) { + case "all": + break; + case "down": + break; + case "resolve": + checksQuery.statusCode = 5000; + break; + default: + console.log("default"); + break; + } + } - // Need to skip and limit here - let skip = 0; - if (page && rowsPerPage) { - skip = page * rowsPerPage; - } - const checks = await Check.find(checksQuery) - .skip(skip) - .limit(rowsPerPage) - .sort({ createdAt: sortOrder }); - return checks; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getChecks"; - throw error; - } + // Need to skip and limit here + let skip = 0; + if (page && rowsPerPage) { + skip = page * rowsPerPage; + } + const checks = await Check.find(checksQuery) + .skip(skip) + .limit(rowsPerPage) + .sort({ createdAt: sortOrder }); + return checks; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getChecks"; + throw error; + } }; const getTeamChecks = async (req) => { - const { teamId } = req.params; - let { sortOrder, limit, dateRange, filter, page, rowsPerPage } = req.query; + const { teamId } = req.params; + let { sortOrder, limit, dateRange, filter, page, rowsPerPage } = req.query; - // Get monitorIDs - const userMonitors = await Monitor.find({ teamId: teamId }).select("_id"); + // Get monitorIDs + const userMonitors = await Monitor.find({ teamId: teamId }).select("_id"); - //Build check query - // Default limit to 0 if not provided - limit = limit === undefined ? 0 : limit; - // Default sort order is newest -> oldest - sortOrder = sortOrder === "asc" ? 1 : -1; + //Build check query + // Default limit to 0 if not provided + limit = limit === undefined ? 0 : limit; + // Default sort order is newest -> oldest + sortOrder = sortOrder === "asc" ? 1 : -1; - checksQuery = { monitorId: { $in: userMonitors } }; + const checksQuery = { monitorId: { $in: userMonitors } }; - if (filter !== undefined) { - checksQuery.status = false; - switch (filter) { - case "all": - break; - case "down": - break; - case "resolve": - checksQuery.statusCode = 5000; - break; - default: - console.log("default"); - break; - } - } + if (filter !== undefined) { + checksQuery.status = false; + switch (filter) { + case "all": + break; + case "down": + break; + case "resolve": + checksQuery.statusCode = 5000; + break; + default: + console.log("default"); + break; + } + } - if (dateRange !== undefined) { - checksQuery.createdAt = { $gte: dateRangeLookup[dateRange] }; - } + if (dateRange !== undefined) { + checksQuery.createdAt = { $gte: dateRangeLookup[dateRange] }; + } - // Skip and limit for pagination - let skip = 0; - if (page && rowsPerPage) { - skip = page * rowsPerPage; - } + // Skip and limit for pagination + let skip = 0; + if (page && rowsPerPage) { + skip = page * rowsPerPage; + } - const checksCount = await Check.countDocuments(checksQuery); + const checksCount = await Check.countDocuments(checksQuery); - const checks = await Check.find(checksQuery) - .skip(skip) - .limit(rowsPerPage) - .sort({ createdAt: sortOrder }) - .select(["monitorId", "status", "responseTime", "statusCode", "message"]); - return { checksCount, checks }; + const checks = await Check.find(checksQuery) + .skip(skip) + .limit(rowsPerPage) + .sort({ createdAt: sortOrder }) + .select(["monitorId", "status", "responseTime", "statusCode", "message"]); + return { checksCount, checks }; }; /** @@ -204,14 +204,14 @@ const getTeamChecks = async (req) => { */ const deleteChecks = async (monitorId) => { - try { - const result = await Check.deleteMany({ monitorId }); - return result.deletedCount; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteChecks"; - throw error; - } + try { + const result = await Check.deleteMany({ monitorId }); + return result.deletedCount; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "deleteChecks"; + throw error; + } }; /** @@ -223,63 +223,63 @@ const deleteChecks = async (monitorId) => { */ const deleteChecksByTeamId = async (teamId) => { - try { - const teamMonitors = await Monitor.find({ teamId: teamId }); - let totalDeletedCount = 0; + try { + const teamMonitors = await Monitor.find({ teamId: teamId }); + let totalDeletedCount = 0; - await Promise.all( - teamMonitors.map(async (monitor) => { - const result = await Check.deleteMany({ monitorId: monitor._id }); - totalDeletedCount += result.deletedCount; - monitor.status = true; - await monitor.save(); - }) - ); + await Promise.all( + teamMonitors.map(async (monitor) => { + const result = await Check.deleteMany({ monitorId: monitor._id }); + totalDeletedCount += result.deletedCount; + monitor.status = true; + await monitor.save(); + }) + ); - return totalDeletedCount; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteChecksByTeamId"; - throw error; - } + return totalDeletedCount; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "deleteChecksByTeamId"; + throw error; + } }; const updateChecksTTL = async (teamId, ttl) => { - try { - await Check.collection.dropIndex("expiry_1"); - } catch (error) { - logger.error("Failed to drop index", { - service: SERVICE_NAME, - method: "updateChecksTTL", - }); - } + try { + await Check.collection.dropIndex("expiry_1"); + } catch (error) { + logger.error("Failed to drop index", { + service: SERVICE_NAME, + method: "updateChecksTTL", + }); + } - try { - await Check.collection.createIndex( - { expiry: 1 }, - { expireAfterSeconds: ttl } // TTL in seconds, adjust as necessary - ); - } catch (error) { - error.service = SERVICE_NAME; - error.method = "updateChecksTTL"; - throw error; - } - // Update user - try { - await User.updateMany({ teamId: teamId }, { checkTTL: ttl }); - } catch (error) { - error.service = SERVICE_NAME; - error.method = "updateChecksTTL"; - throw error; - } + try { + await Check.collection.createIndex( + { expiry: 1 }, + { expireAfterSeconds: ttl } // TTL in seconds, adjust as necessary + ); + } catch (error) { + error.service = SERVICE_NAME; + error.method = "updateChecksTTL"; + throw error; + } + // Update user + try { + await User.updateMany({ teamId: teamId }, { checkTTL: ttl }); + } catch (error) { + error.service = SERVICE_NAME; + error.method = "updateChecksTTL"; + throw error; + } }; export { - createCheck, - getChecksCount, - getChecks, - getTeamChecks, - deleteChecks, - deleteChecksByTeamId, - updateChecksTTL, + createCheck, + getChecksCount, + getChecks, + getTeamChecks, + deleteChecks, + deleteChecksByTeamId, + updateChecksTTL, }; diff --git a/Server/db/mongo/modules/hardwareCheckModule.js b/Server/db/mongo/modules/hardwareCheckModule.js new file mode 100644 index 000000000..de46828e5 --- /dev/null +++ b/Server/db/mongo/modules/hardwareCheckModule.js @@ -0,0 +1,16 @@ +import HardwareCheck from "../../models/HardwareCheck.js"; +const SERVICE_NAME = "hardwareCheckModule"; +const createHardwareCheck = async (hardwareCheckData) => { + try { + const hardwareCheck = await new HardwareCheck({ + ...hardwareCheckData, + }).save(); + return hardwareCheck; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "createHardwareCheck"; + throw error; + } +}; + +export { createHardwareCheck }; diff --git a/Server/db/mongo/modules/inviteModule.js b/Server/db/mongo/modules/inviteModule.js index c2a8eecc4..63636be86 100644 --- a/Server/db/mongo/modules/inviteModule.js +++ b/Server/db/mongo/modules/inviteModule.js @@ -18,17 +18,17 @@ const SERVICE_NAME = "inviteModule"; * @throws {Error} If there is an error. */ const requestInviteToken = async (userData) => { - try { - await InviteToken.deleteMany({ email: userData.email }); - userData.token = crypto.randomBytes(32).toString("hex"); - let inviteToken = new InviteToken(userData); - await inviteToken.save(); - return inviteToken; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "requestInviteToken"; - throw error; - } + try { + await InviteToken.deleteMany({ email: userData.email }); + userData.token = crypto.randomBytes(32).toString("hex"); + let inviteToken = new InviteToken(userData); + await inviteToken.save(); + return inviteToken; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "requestInviteToken"; + throw error; + } }; /** @@ -42,19 +42,19 @@ const requestInviteToken = async (userData) => { * @throws {Error} If the invite token is not found or there is another error. */ const getInviteToken = async (token) => { - try { - const invite = await InviteToken.findOne({ - token, - }); - if (invite === null) { - throw new Error(errorMessages.AUTH_INVITE_NOT_FOUND); - } - return invite; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getInviteToken"; - throw error; - } + try { + const invite = await InviteToken.findOne({ + token, + }); + if (invite === null) { + throw new Error(errorMessages.AUTH_INVITE_NOT_FOUND); + } + return invite; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getInviteToken"; + throw error; + } }; /** @@ -68,19 +68,19 @@ const getInviteToken = async (token) => { * @throws {Error} If the invite token is not found or there is another error. */ const getInviteTokenAndDelete = async (token) => { - try { - const invite = await InviteToken.findOneAndDelete({ - token, - }); - if (invite === null) { - throw new Error(errorMessages.AUTH_INVITE_NOT_FOUND); - } - return invite; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getInviteToken"; - throw error; - } + try { + const invite = await InviteToken.findOneAndDelete({ + token, + }); + if (invite === null) { + throw new Error(errorMessages.AUTH_INVITE_NOT_FOUND); + } + return invite; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getInviteToken"; + throw error; + } }; export { requestInviteToken, getInviteToken, getInviteTokenAndDelete }; diff --git a/Server/db/mongo/modules/maintenanceWindowModule.js b/Server/db/mongo/modules/maintenanceWindowModule.js index 173af5467..4644d90bf 100644 --- a/Server/db/mongo/modules/maintenanceWindowModule.js +++ b/Server/db/mongo/modules/maintenanceWindowModule.js @@ -27,35 +27,35 @@ const SERVICE_NAME = "maintenanceWindowModule"; * .catch(error => console.error(error)); */ const createMaintenanceWindow = async (maintenanceWindowData) => { - try { - const maintenanceWindow = new MaintenanceWindow({ - ...maintenanceWindowData, - }); + try { + const maintenanceWindow = new MaintenanceWindow({ + ...maintenanceWindowData, + }); - // If the maintenance window is a one time window, set the expiry to the end date - if (maintenanceWindowData.oneTime) { - maintenanceWindow.expiry = maintenanceWindowData.end; - } - const result = await maintenanceWindow.save(); - return result; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "createMaintenanceWindow"; - throw error; - } + // If the maintenance window is a one time window, set the expiry to the end date + if (maintenanceWindowData.oneTime) { + maintenanceWindow.expiry = maintenanceWindowData.end; + } + const result = await maintenanceWindow.save(); + return result; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "createMaintenanceWindow"; + throw error; + } }; const getMaintenanceWindowById = async (maintenanceWindowId) => { - try { - const maintenanceWindow = await MaintenanceWindow.findById({ - _id: maintenanceWindowId, - }); - return maintenanceWindow; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getMaintenanceWindowById"; - throw error; - } + try { + const maintenanceWindow = await MaintenanceWindow.findById({ + _id: maintenanceWindowId, + }); + return maintenanceWindow; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getMaintenanceWindowById"; + throw error; + } }; /** @@ -72,38 +72,38 @@ const getMaintenanceWindowById = async (maintenanceWindowId) => { * .catch(error => console.error(error)); */ const getMaintenanceWindowsByTeamId = async (teamId, query) => { - try { - let { active, page, rowsPerPage, field, order } = query || {}; - const maintenanceQuery = { teamId }; + try { + let { active, page, rowsPerPage, field, order } = query || {}; + const maintenanceQuery = { teamId }; - if (active !== undefined) maintenanceQuery.active = active; + if (active !== undefined) maintenanceQuery.active = active; - const maintenanceWindowCount = - await MaintenanceWindow.countDocuments(maintenanceQuery); + const maintenanceWindowCount = + await MaintenanceWindow.countDocuments(maintenanceQuery); - // Pagination - let skip = 0; - if (page && rowsPerPage) { - skip = page * rowsPerPage; - } + // Pagination + let skip = 0; + if (page && rowsPerPage) { + skip = page * rowsPerPage; + } - // Sorting - let sort = {}; - if (field !== undefined && order !== undefined) { - sort[field] = order === "asc" ? 1 : -1; - } + // Sorting + let sort = {}; + if (field !== undefined && order !== undefined) { + sort[field] = order === "asc" ? 1 : -1; + } - const maintenanceWindows = await MaintenanceWindow.find(maintenanceQuery) - .skip(skip) - .limit(rowsPerPage) - .sort(sort); + const maintenanceWindows = await MaintenanceWindow.find(maintenanceQuery) + .skip(skip) + .limit(rowsPerPage) + .sort(sort); - return { maintenanceWindows, maintenanceWindowCount }; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getMaintenanceWindowByUserId"; - throw error; - } + return { maintenanceWindows, maintenanceWindowCount }; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getMaintenanceWindowByUserId"; + throw error; + } }; /** @@ -119,16 +119,16 @@ const getMaintenanceWindowsByTeamId = async (teamId, query) => { * .catch(error => console.error(error)); */ const getMaintenanceWindowsByMonitorId = async (monitorId) => { - try { - const maintenanceWindows = await MaintenanceWindow.find({ - monitorId: monitorId, - }); - return maintenanceWindows; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getMaintenanceWindowsByMonitorId"; - throw error; - } + try { + const maintenanceWindows = await MaintenanceWindow.find({ + monitorId: monitorId, + }); + return maintenanceWindows; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getMaintenanceWindowsByMonitorId"; + throw error; + } }; /** @@ -144,15 +144,15 @@ const getMaintenanceWindowsByMonitorId = async (monitorId) => { * .catch(error => console.error(error)); */ const deleteMaintenanceWindowById = async (maintenanceWindowId) => { - try { - const maintenanceWindow = - await MaintenanceWindow.findByIdAndDelete(maintenanceWindowId); - return maintenanceWindow; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteMaintenanceWindowById"; - throw error; - } + try { + const maintenanceWindow = + await MaintenanceWindow.findByIdAndDelete(maintenanceWindowId); + return maintenanceWindow; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "deleteMaintenanceWindowById"; + throw error; + } }; /** @@ -168,14 +168,14 @@ const deleteMaintenanceWindowById = async (maintenanceWindowId) => { * .catch(error => console.error(error)); */ const deleteMaintenanceWindowByMonitorId = async (monitorId) => { - try { - const result = await MaintenanceWindow.deleteMany({ monitorId: monitorId }); - return result; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteMaintenanceWindowByMonitorId"; - throw error; - } + try { + const result = await MaintenanceWindow.deleteMany({ monitorId: monitorId }); + return result; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "deleteMaintenanceWindowByMonitorId"; + throw error; + } }; /** @@ -191,42 +191,39 @@ const deleteMaintenanceWindowByMonitorId = async (monitorId) => { * .catch(error => console.error(error)); */ const deleteMaintenanceWindowByUserId = async (userId) => { - try { - const result = await MaintenanceWindow.deleteMany({ userId: userId }); - return result; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteMaintenanceWindowByUserId"; - throw error; - } + try { + const result = await MaintenanceWindow.deleteMany({ userId: userId }); + return result; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "deleteMaintenanceWindowByUserId"; + throw error; + } }; -const editMaintenanceWindowById = async ( - maintenanceWindowId, - maintenanceWindowData -) => { - console.log(maintenanceWindowData); - try { - const editedMaintenanceWindow = MaintenanceWindow.findByIdAndUpdate( - maintenanceWindowId, - maintenanceWindowData, - { new: true } - ); - return editedMaintenanceWindow; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "editMaintenanceWindowById"; - throw error; - } +const editMaintenanceWindowById = async (maintenanceWindowId, maintenanceWindowData) => { + console.log(maintenanceWindowData); + try { + const editedMaintenanceWindow = MaintenanceWindow.findByIdAndUpdate( + maintenanceWindowId, + maintenanceWindowData, + { new: true } + ); + return editedMaintenanceWindow; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "editMaintenanceWindowById"; + throw error; + } }; export { - createMaintenanceWindow, - getMaintenanceWindowById, - getMaintenanceWindowsByTeamId, - getMaintenanceWindowsByMonitorId, - deleteMaintenanceWindowById, - deleteMaintenanceWindowByMonitorId, - deleteMaintenanceWindowByUserId, - editMaintenanceWindowById, + createMaintenanceWindow, + getMaintenanceWindowById, + getMaintenanceWindowsByTeamId, + getMaintenanceWindowsByMonitorId, + deleteMaintenanceWindowById, + deleteMaintenanceWindowByMonitorId, + deleteMaintenanceWindowByUserId, + editMaintenanceWindowById, }; diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js index 847d07841..5d54db393 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -11,10 +11,7 @@ import { fileURLToPath } from "url"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); -const demoMonitorsPath = path.resolve( - __dirname, - "../../../utils/demoMonitors.json" -); +const demoMonitorsPath = path.resolve(__dirname, "../../../utils/demoMonitors.json"); const demoMonitors = JSON.parse(fs.readFileSync(demoMonitorsPath, "utf8")); const SERVICE_NAME = "monitorModule"; @@ -28,14 +25,14 @@ const SERVICE_NAME = "monitorModule"; * @throws {Error} */ const getAllMonitors = async (req, res) => { - try { - const monitors = await Monitor.find(); - return monitors; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getAllMonitors"; - throw error; - } + try { + const monitors = await Monitor.find(); + return monitors; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getAllMonitors"; + throw error; + } }; /** @@ -44,27 +41,27 @@ const getAllMonitors = async (req, res) => { * @returns {number} Uptime duration in ms. */ const calculateUptimeDuration = (checks) => { - if (!checks || checks.length === 0) { - return 0; - } + if (!checks || checks.length === 0) { + return 0; + } - const latestCheck = new Date(checks[0].createdAt); - let latestDownCheck = 0; + const latestCheck = new Date(checks[0].createdAt); + let latestDownCheck = 0; - for (let i = checks.length; i <= 0; i--) { - if (checks[i].status === false) { - latestDownCheck = new Date(checks[i].createdAt); - break; - } - } + for (let i = checks.length; i <= 0; i--) { + if (checks[i].status === false) { + latestDownCheck = new Date(checks[i].createdAt); + break; + } + } - // If no down check is found, uptime is from the last check to now - if (latestDownCheck === 0) { - return Date.now() - new Date(checks[checks.length - 1].createdAt); - } + // If no down check is found, uptime is from the last check to now + if (latestDownCheck === 0) { + return Date.now() - new Date(checks[checks.length - 1].createdAt); + } - // Otherwise the uptime is from the last check to the last down check - return latestCheck - latestDownCheck; + // Otherwise the uptime is from the last check to the last down check + return latestCheck - latestDownCheck; }; /** @@ -73,11 +70,11 @@ const calculateUptimeDuration = (checks) => { * @returns {number} Timestamp of the most recent check. */ const getLastChecked = (checks) => { - if (!checks || checks.length === 0) { - return 0; // Handle case when no checks are available - } - // Data is sorted newest->oldest, so last check is the most recent - return new Date() - new Date(checks[0].createdAt); + if (!checks || checks.length === 0) { + return 0; // Handle case when no checks are available + } + // Data is sorted newest->oldest, so last check is the most recent + return new Date() - new Date(checks[0].createdAt); }; /** @@ -86,10 +83,10 @@ const getLastChecked = (checks) => { * @returns {number} Timestamp of the most recent check. */ const getLatestResponseTime = (checks) => { - if (!checks || checks.length === 0) { - return 0; - } - return checks[0].responseTime; + if (!checks || checks.length === 0) { + return 0; + } + return checks[0].responseTime; }; /** @@ -98,13 +95,13 @@ const getLatestResponseTime = (checks) => { * @returns {number} Timestamp of the most recent check. */ const getAverageResponseTime = (checks) => { - if (!checks || checks.length === 0) { - return 0; - } - const aggResponseTime = checks.reduce((sum, check) => { - return sum + check.responseTime; - }, 0); - return aggResponseTime / checks.length; + if (!checks || checks.length === 0) { + return 0; + } + const aggResponseTime = checks.reduce((sum, check) => { + return sum + check.responseTime; + }, 0); + return aggResponseTime / checks.length; }; /** @@ -114,13 +111,13 @@ const getAverageResponseTime = (checks) => { */ const getUptimePercentage = (checks) => { - if (!checks || checks.length === 0) { - return 0; - } - const upCount = checks.reduce((count, check) => { - return check.status === true ? count + 1 : count; - }, 0); - return (upCount / checks.length) * 100; + if (!checks || checks.length === 0) { + return 0; + } + const upCount = checks.reduce((count, check) => { + return check.status === true ? count + 1 : count; + }, 0); + return (upCount / checks.length) * 100; }; /** @@ -130,12 +127,12 @@ const getUptimePercentage = (checks) => { */ const getIncidents = (checks) => { - if (!checks || checks.length === 0) { - return 0; // Handle case when no checks are available - } - return checks.reduce((acc, check) => { - return check.status === false ? (acc += 1) : acc; - }, 0); + if (!checks || checks.length === 0) { + return 0; // Handle case when no checks are available + } + return checks.reduce((acc, check) => { + return check.status === false ? (acc += 1) : acc; + }, 0); }; /** @@ -147,140 +144,136 @@ const getIncidents = (checks) => { * @throws {Error} */ const getMonitorStatsById = async (req) => { - const startDates = { - day: new Date(new Date().setDate(new Date().getDate() - 1)), - week: new Date(new Date().setDate(new Date().getDate() - 7)), - month: new Date(new Date().setMonth(new Date().getMonth() - 1)), - }; - const endDate = new Date(); - try { - // Get monitor - const { monitorId } = req.params; - let { limit, sortOrder, dateRange, numToDisplay, normalize } = req.query; - const monitor = await Monitor.findById(monitorId); - if (monitor === null || monitor === undefined) { - throw new Error(errorMessages.DB_FIND_MONITOR_BY_ID(monitorId)); - } - // This effectively removes limit, returning all checks - if (limit === undefined) limit = 0; - // Default sort order is newest -> oldest - sortOrder = sortOrder === "asc" ? 1 : -1; + const startDates = { + day: new Date(new Date().setDate(new Date().getDate() - 1)), + week: new Date(new Date().setDate(new Date().getDate() - 7)), + month: new Date(new Date().setMonth(new Date().getMonth() - 1)), + }; + const endDate = new Date(); + try { + // Get monitor + const { monitorId } = req.params; + let { limit, sortOrder, dateRange, numToDisplay, normalize } = req.query; + const monitor = await Monitor.findById(monitorId); + if (monitor === null || monitor === undefined) { + throw new Error(errorMessages.DB_FIND_MONITOR_BY_ID(monitorId)); + } + // This effectively removes limit, returning all checks + if (limit === undefined) limit = 0; + // Default sort order is newest -> oldest + sortOrder = sortOrder === "asc" ? 1 : -1; - let model = - monitor.type === "http" || monitor.type === "ping" - ? Check - : PageSpeedCheck; + let model = + monitor.type === "http" || monitor.type === "ping" ? Check : PageSpeedCheck; - const monitorStats = { - ...monitor.toObject(), - }; + const monitorStats = { + ...monitor.toObject(), + }; - // Build checks query - const checksQuery = { monitorId: monitor._id }; + // Build checks query + const checksQuery = { monitorId: monitor._id }; - // Get all checks - const checksAll = await model.find(checksQuery).sort({ - createdAt: sortOrder, - }); + // Get all checks + const checksAll = await model.find(checksQuery).sort({ + createdAt: sortOrder, + }); - const checksQueryForDateRange = { - ...checksQuery, - createdAt: { - $gte: startDates[dateRange], - $lte: endDate, - }, - }; + const checksQueryForDateRange = { + ...checksQuery, + createdAt: { + $gte: startDates[dateRange], + $lte: endDate, + }, + }; - const checksForDateRange = await model - .find(checksQueryForDateRange) - .sort({ createdAt: sortOrder }); + const checksForDateRange = await model + .find(checksQueryForDateRange) + .sort({ createdAt: sortOrder }); - if (monitor.type === "http" || monitor.type === "ping") { - // HTTP/PING Specific stats - monitorStats.periodAvgResponseTime = - getAverageResponseTime(checksForDateRange); - monitorStats.periodUptime = getUptimePercentage(checksForDateRange); + if (monitor.type === "http" || monitor.type === "ping") { + // HTTP/PING Specific stats + monitorStats.periodAvgResponseTime = getAverageResponseTime(checksForDateRange); + monitorStats.periodUptime = getUptimePercentage(checksForDateRange); - // Aggregate data - let groupedChecks; - // Group checks by hour if range is day - if (dateRange === "day") { - groupedChecks = checksForDateRange.reduce((acc, check) => { - const time = new Date(check.createdAt); - time.setMinutes(0, 0, 0); - if (!acc[time]) { - acc[time] = { time, checks: [] }; - } - acc[time].checks.push(check); - return acc; - }, {}); - } else { - groupedChecks = checksForDateRange.reduce((acc, check) => { - const time = new Date(check.createdAt).toISOString().split("T")[0]; // Extract the date part - if (!acc[time]) { - acc[time] = { time, checks: [] }; - } - acc[time].checks.push(check); - return acc; - }, {}); - } + // Aggregate data + let groupedChecks; + // Group checks by hour if range is day + if (dateRange === "day") { + groupedChecks = checksForDateRange.reduce((acc, check) => { + const time = new Date(check.createdAt); + time.setMinutes(0, 0, 0); + if (!acc[time]) { + acc[time] = { time, checks: [] }; + } + acc[time].checks.push(check); + return acc; + }, {}); + } else { + groupedChecks = checksForDateRange.reduce((acc, check) => { + const time = new Date(check.createdAt).toISOString().split("T")[0]; // Extract the date part + if (!acc[time]) { + acc[time] = { time, checks: [] }; + } + acc[time].checks.push(check); + return acc; + }, {}); + } - // Map grouped checks to stats - const aggregateData = Object.values(groupedChecks).map((group) => { - const totalChecks = group.checks.length; - const uptimePercentage = getUptimePercentage(group.checks); - const totalIncidents = group.checks.filter( - (check) => check.status === false - ).length; - const avgResponseTime = - group.checks.reduce((sum, check) => sum + check.responseTime, 0) / - totalChecks; + // Map grouped checks to stats + const aggregateData = Object.values(groupedChecks).map((group) => { + const totalChecks = group.checks.length; + const uptimePercentage = getUptimePercentage(group.checks); + const totalIncidents = group.checks.filter( + (check) => check.status === false + ).length; + const avgResponseTime = + group.checks.reduce((sum, check) => sum + check.responseTime, 0) / totalChecks; - return { - time: group.time, - uptimePercentage, - totalChecks, - totalIncidents, - avgResponseTime, - }; - }); - monitorStats.aggregateData = aggregateData; - } + return { + time: group.time, + uptimePercentage, + totalChecks, + totalIncidents, + avgResponseTime, + }; + }); + monitorStats.aggregateData = aggregateData; + } - monitorStats.periodIncidents = getIncidents(checksForDateRange); - monitorStats.periodTotalChecks = checksForDateRange.length; + monitorStats.periodIncidents = getIncidents(checksForDateRange); + monitorStats.periodTotalChecks = checksForDateRange.length; - // If more than numToDisplay checks, pick every nth check + // If more than numToDisplay checks, pick every nth check - let nthChecks = checksForDateRange; + let nthChecks = checksForDateRange; - if ( - numToDisplay !== undefined && - checksForDateRange && - checksForDateRange.length > numToDisplay - ) { - const n = Math.ceil(checksForDateRange.length / numToDisplay); - nthChecks = checksForDateRange.filter((_, index) => index % n === 0); - } + if ( + numToDisplay !== undefined && + checksForDateRange && + checksForDateRange.length > numToDisplay + ) { + const n = Math.ceil(checksForDateRange.length / numToDisplay); + nthChecks = checksForDateRange.filter((_, index) => index % n === 0); + } - // Normalize checks if requested - if (normalize !== undefined) { - const normailzedChecks = NormalizeData(nthChecks, 1, 100); - monitorStats.checks = normailzedChecks; - } else { - monitorStats.checks = nthChecks; - } + // Normalize checks if requested + if (normalize !== undefined) { + const normailzedChecks = NormalizeData(nthChecks, 1, 100); + monitorStats.checks = normailzedChecks; + } else { + monitorStats.checks = nthChecks; + } - monitorStats.uptimeDuration = calculateUptimeDuration(checksAll); - monitorStats.lastChecked = getLastChecked(checksAll); - monitorStats.latestResponseTime = getLatestResponseTime(checksAll); + monitorStats.uptimeDuration = calculateUptimeDuration(checksAll); + monitorStats.lastChecked = getLastChecked(checksAll); + monitorStats.latestResponseTime = getLatestResponseTime(checksAll); - return monitorStats; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getMonitorStatsById"; - throw error; - } + return monitorStats; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getMonitorStatsById"; + throw error; + } }; /** @@ -292,23 +285,23 @@ const getMonitorStatsById = async (req) => { * @throws {Error} */ const getMonitorById = async (monitorId) => { - try { - const monitor = await Monitor.findById(monitorId); - if (monitor === null || monitor === undefined) { - throw new Error(errorMessages.DB_FIND_MONITOR_BY_ID(monitorId)); - } - // Get notifications - const notifications = await Notification.find({ - monitorId: monitorId, - }); - monitor.notifications = notifications; - const monitorWithNotifications = await monitor.save(); - return monitorWithNotifications; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getMonitorById"; - throw error; - } + try { + const monitor = await Monitor.findById(monitorId); + if (monitor === null || monitor === undefined) { + throw new Error(errorMessages.DB_FIND_MONITOR_BY_ID(monitorId)); + } + // Get notifications + const notifications = await Notification.find({ + monitorId: monitorId, + }); + monitor.notifications = notifications; + const monitorWithNotifications = await monitor.save(); + return monitorWithNotifications; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getMonitorById"; + throw error; + } }; /** @@ -321,32 +314,32 @@ const getMonitorById = async (monitorId) => { */ const getMonitorsAndSummaryByTeamId = async (teamId, type) => { - try { - const monitors = await Monitor.find({ teamId, type }); - const monitorCounts = monitors.reduce( - (acc, monitor) => { - if (monitor.status === true) { - acc.up += 1; - } + try { + const monitors = await Monitor.find({ teamId, type }); + const monitorCounts = monitors.reduce( + (acc, monitor) => { + if (monitor.status === true) { + acc.up += 1; + } - if (monitor.status === false) { - acc.down += 1; - } + if (monitor.status === false) { + acc.down += 1; + } - if (monitor.isActive === false) { - acc.paused += 1; - } - return acc; - }, - { up: 0, down: 0, paused: 0 } - ); - monitorCounts.total = monitors.length; - return { monitors, monitorCounts }; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getMonitorsAndSummaryByTeamId"; - throw error; - } + if (monitor.isActive === false) { + acc.paused += 1; + } + return acc; + }, + { up: 0, down: 0, paused: 0 } + ); + monitorCounts.total = monitors.length; + return { monitors, monitorCounts }; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getMonitorsAndSummaryByTeamId"; + throw error; + } }; /** @@ -358,104 +351,102 @@ const getMonitorsAndSummaryByTeamId = async (teamId, type) => { * @throws {Error} */ const getMonitorsByTeamId = async (req, res) => { - try { - let { - limit, - type, - status, - checkOrder, - normalize, - page, - rowsPerPage, - filter, - field, - order, - } = req.query || {}; - const monitorQuery = { teamId: req.params.teamId }; - if (type !== undefined) { - monitorQuery.type = type; - } - // Add filter if provided - // $options: "i" makes the search case-insensitive - if (filter !== undefined) { - monitorQuery.$or = [ - { name: { $regex: filter, $options: "i" } }, - { url: { $regex: filter, $options: "i" } }, - ]; - } - const monitorsCount = await Monitor.countDocuments(monitorQuery); + try { + let { + limit, + type, + status, + checkOrder, + normalize, + page, + rowsPerPage, + filter, + field, + order, + } = req.query || {}; + const monitorQuery = { teamId: req.params.teamId }; + if (type !== undefined) { + monitorQuery.type = type; + } + // Add filter if provided + // $options: "i" makes the search case-insensitive + if (filter !== undefined) { + monitorQuery.$or = [ + { name: { $regex: filter, $options: "i" } }, + { url: { $regex: filter, $options: "i" } }, + ]; + } + const monitorsCount = await Monitor.countDocuments(monitorQuery); - // Pagination - let skip = 0; - if (page && rowsPerPage) { - skip = page * rowsPerPage; - } + // Pagination + let skip = 0; + if (page && rowsPerPage) { + skip = page * rowsPerPage; + } - if (type !== undefined) { - const types = Array.isArray(type) ? type : [type]; - monitorQuery.type = { $in: types }; - } + if (type !== undefined) { + const types = Array.isArray(type) ? type : [type]; + monitorQuery.type = { $in: types }; + } - // Default sort order is newest -> oldest - if (checkOrder === "asc") { - checkOrder = 1; - } else if (checkOrder === "desc") { - checkOrder = -1; - } else checkOrder = -1; + // Default sort order is newest -> oldest + if (checkOrder === "asc") { + checkOrder = 1; + } else if (checkOrder === "desc") { + checkOrder = -1; + } else checkOrder = -1; - // Sort order for monitors - let sort = {}; - if (field !== undefined && order !== undefined) { - sort[field] = order === "asc" ? 1 : -1; - } + // Sort order for monitors + let sort = {}; + if (field !== undefined && order !== undefined) { + sort[field] = order === "asc" ? 1 : -1; + } - const monitors = await Monitor.find(monitorQuery) - .skip(skip) - .limit(rowsPerPage) - .sort(sort); + const monitors = await Monitor.find(monitorQuery) + .skip(skip) + .limit(rowsPerPage) + .sort(sort); - // Early return if limit is set to -1, indicating we don't want any checks - if (limit === "-1") { - return { monitors, monitorCount: monitorsCount }; - } + // Early return if limit is set to -1, indicating we don't want any checks + if (limit === "-1") { + return { monitors, monitorCount: monitorsCount }; + } - // This effectively removes limit, returning all checks - if (limit === undefined) limit = 0; + // This effectively removes limit, returning all checks + if (limit === undefined) limit = 0; - // Map each monitor to include its associated checks - const monitorsWithChecks = await Promise.all( - monitors.map(async (monitor) => { - const checksQuery = { monitorId: monitor._id }; - if (status !== undefined) { - checksQuery.status = status; - } + // Map each monitor to include its associated checks + const monitorsWithChecks = await Promise.all( + monitors.map(async (monitor) => { + const checksQuery = { monitorId: monitor._id }; + if (status !== undefined) { + checksQuery.status = status; + } - let model = - monitor.type === "http" || monitor.type === "ping" - ? Check - : PageSpeedCheck; + let model = + monitor.type === "http" || monitor.type === "ping" ? Check : PageSpeedCheck; - // Checks are order newest -> oldest - let checks = await model - .find(checksQuery) - .sort({ - createdAt: checkOrder, - }) - .limit(limit); + // Checks are order newest -> oldest + let checks = await model + .find(checksQuery) + .sort({ + createdAt: checkOrder, + }) + .limit(limit); - //Normalize checks if requested - if (normalize !== undefined) { - checks = NormalizeData(checks, 10, 100); - } - return { ...monitor.toObject(), checks }; - }) - ); - return { monitors: monitorsWithChecks, monitorCount: monitorsCount }; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getMonitorsByTeamId"; - throw error; - } + //Normalize checks if requested + if (normalize !== undefined) { + checks = NormalizeData(checks, 10, 100); + } + return { ...monitor.toObject(), checks }; + }) + ); + return { monitors: monitorsWithChecks, monitorCount: monitorsCount }; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getMonitorsByTeamId"; + throw error; + } }; /** @@ -467,17 +458,17 @@ const getMonitorsByTeamId = async (req, res) => { * @throws {Error} */ const createMonitor = async (req, res) => { - try { - const monitor = new Monitor({ ...req.body }); - // Remove notifications fom monitor as they aren't needed here - monitor.notifications = undefined; - await monitor.save(); - return monitor; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "createMonitor"; - throw error; - } + try { + const monitor = new Monitor({ ...req.body }); + // Remove notifications fom monitor as they aren't needed here + monitor.notifications = undefined; + await monitor.save(); + return monitor; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "createMonitor"; + throw error; + } }; /** @@ -489,18 +480,18 @@ const createMonitor = async (req, res) => { * @throws {Error} */ const deleteMonitor = async (req, res) => { - const monitorId = req.params.monitorId; - try { - const monitor = await Monitor.findByIdAndDelete(monitorId); - if (!monitor) { - throw new Error(errorMessages.DB_FIND_MONITOR_BY_ID(monitorId)); - } - return monitor; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteMonitor"; - throw error; - } + const monitorId = req.params.monitorId; + try { + const monitor = await Monitor.findByIdAndDelete(monitorId); + if (!monitor) { + throw new Error(errorMessages.DB_FIND_MONITOR_BY_ID(monitorId)); + } + return monitor; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "deleteMonitor"; + throw error; + } }; /** @@ -508,16 +499,16 @@ const deleteMonitor = async (req, res) => { */ const deleteAllMonitors = async (teamId) => { - try { - const monitors = await Monitor.find({ teamId }); - const { deletedCount } = await Monitor.deleteMany({ teamId }); + try { + const monitors = await Monitor.find({ teamId }); + const { deletedCount } = await Monitor.deleteMany({ teamId }); - return { monitors, deletedCount }; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteAllMonitors"; - throw error; - } + return { monitors, deletedCount }; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "deleteAllMonitors"; + throw error; + } }; /** @@ -527,14 +518,14 @@ const deleteAllMonitors = async (teamId) => { * @returns {Promise} A promise that resolves when the operation is complete. */ const deleteMonitorsByUserId = async (userId) => { - try { - const result = await Monitor.deleteMany({ userId: userId }); - return result; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteMonitorsByUserId"; - throw error; - } + try { + const result = await Monitor.deleteMany({ userId: userId }); + return result; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "deleteMonitorsByUserId"; + throw error; + } }; /** @@ -546,54 +537,52 @@ const deleteMonitorsByUserId = async (userId) => { * @throws {Error} */ const editMonitor = async (candidateId, candidateMonitor) => { - candidateMonitor.notifications = undefined; + candidateMonitor.notifications = undefined; - try { - const editedMonitor = await Monitor.findByIdAndUpdate( - candidateId, - candidateMonitor, - { new: true } - ); - return editedMonitor; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "editMonitor"; - throw error; - } + try { + const editedMonitor = await Monitor.findByIdAndUpdate(candidateId, candidateMonitor, { + new: true, + }); + return editedMonitor; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "editMonitor"; + throw error; + } }; const addDemoMonitors = async (userId, teamId) => { - try { - const demoMonitorsToInsert = demoMonitors.map((monitor) => { - return { - userId, - teamId, - name: monitor.name, - description: monitor.name, - type: "http", - url: monitor.url, - interval: 60000, - }; - }); - const insertedMonitors = await Monitor.insertMany(demoMonitorsToInsert); - return insertedMonitors; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "addDemoMonitors"; - throw error; - } + try { + const demoMonitorsToInsert = demoMonitors.map((monitor) => { + return { + userId, + teamId, + name: monitor.name, + description: monitor.name, + type: "http", + url: monitor.url, + interval: 60000, + }; + }); + const insertedMonitors = await Monitor.insertMany(demoMonitorsToInsert); + return insertedMonitors; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "addDemoMonitors"; + throw error; + } }; export { - getAllMonitors, - getMonitorStatsById, - getMonitorById, - getMonitorsAndSummaryByTeamId, - getMonitorsByTeamId, - createMonitor, - deleteMonitor, - deleteAllMonitors, - deleteMonitorsByUserId, - editMonitor, - addDemoMonitors, + getAllMonitors, + getMonitorStatsById, + getMonitorById, + getMonitorsAndSummaryByTeamId, + getMonitorsByTeamId, + createMonitor, + deleteMonitor, + deleteAllMonitors, + deleteMonitorsByUserId, + editMonitor, + addDemoMonitors, }; diff --git a/Server/db/mongo/modules/notificationModule.js b/Server/db/mongo/modules/notificationModule.js index ec690b5d2..aab2d03bd 100644 --- a/Server/db/mongo/modules/notificationModule.js +++ b/Server/db/mongo/modules/notificationModule.js @@ -11,14 +11,14 @@ const SERVICE_NAME = "notificationModule"; * @throws Will throw an error if the notification cannot be created. */ const createNotification = async (notificationData) => { - try { - const notification = await new Notification({ ...notificationData }).save(); - return notification; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "createNotification"; - throw error; - } + try { + const notification = await new Notification({ ...notificationData }).save(); + return notification; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "createNotification"; + throw error; + } }; /** @@ -28,29 +28,29 @@ const createNotification = async (notificationData) => { * @throws Will throw an error if the notifications cannot be retrieved. */ const getNotificationsByMonitorId = async (monitorId) => { - try { - const notifications = await Notification.find({ monitorId }); - return notifications; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getNotificationsByMonitorId"; - throw error; - } + try { + const notifications = await Notification.find({ monitorId }); + return notifications; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getNotificationsByMonitorId"; + throw error; + } }; const deleteNotificationsByMonitorId = async (monitorId) => { - try { - const result = await Notification.deleteMany({ monitorId }); - return result.deletedCount; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteNotificationsByMonitorId"; - throw error; - } + try { + const result = await Notification.deleteMany({ monitorId }); + return result.deletedCount; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "deleteNotificationsByMonitorId"; + throw error; + } }; export { - createNotification, - getNotificationsByMonitorId, - deleteNotificationsByMonitorId, + createNotification, + getNotificationsByMonitorId, + deleteNotificationsByMonitorId, }; diff --git a/Server/db/mongo/modules/pageSpeedCheckModule.js b/Server/db/mongo/modules/pageSpeedCheckModule.js index be3dda2d9..56dafa697 100644 --- a/Server/db/mongo/modules/pageSpeedCheckModule.js +++ b/Server/db/mongo/modules/pageSpeedCheckModule.js @@ -13,16 +13,16 @@ const SERVICE_NAME = "pageSpeedCheckModule"; * @throws {Error} */ const createPageSpeedCheck = async (pageSpeedCheckData) => { - try { - const pageSpeedCheck = await new PageSpeedCheck({ - ...pageSpeedCheckData, - }).save(); - return pageSpeedCheck; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "createPageSpeedCheck"; - throw error; - } + try { + const pageSpeedCheck = await new PageSpeedCheck({ + ...pageSpeedCheckData, + }).save(); + return pageSpeedCheck; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "createPageSpeedCheck"; + throw error; + } }; /** @@ -34,14 +34,14 @@ const createPageSpeedCheck = async (pageSpeedCheckData) => { */ const getPageSpeedChecks = async (monitorId) => { - try { - const pageSpeedChecks = await PageSpeedCheck.find({ monitorId }); - return pageSpeedChecks; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getPageSpeedChecks"; - throw error; - } + try { + const pageSpeedChecks = await PageSpeedCheck.find({ monitorId }); + return pageSpeedChecks; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getPageSpeedChecks"; + throw error; + } }; /** @@ -53,18 +53,14 @@ const getPageSpeedChecks = async (monitorId) => { */ const deletePageSpeedChecksByMonitorId = async (monitorId) => { - try { - const result = await PageSpeedCheck.deleteMany({ monitorId }); - return result.deletedCount; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deletePageSpeedChecksByMonitorId"; - throw error; - } + try { + const result = await PageSpeedCheck.deleteMany({ monitorId }); + return result.deletedCount; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "deletePageSpeedChecksByMonitorId"; + throw error; + } }; -export { - createPageSpeedCheck, - getPageSpeedChecks, - deletePageSpeedChecksByMonitorId, -}; +export { createPageSpeedCheck, getPageSpeedChecks, deletePageSpeedChecksByMonitorId }; diff --git a/Server/db/mongo/modules/recoveryModule.js b/Server/db/mongo/modules/recoveryModule.js index e9faa5a3c..e061fe664 100644 --- a/Server/db/mongo/modules/recoveryModule.js +++ b/Server/db/mongo/modules/recoveryModule.js @@ -14,72 +14,72 @@ const SERVICE_NAME = "recoveryModule"; * @throws {Error} */ const requestRecoveryToken = async (req, res) => { - try { - // Delete any existing tokens - await RecoveryToken.deleteMany({ email: req.body.email }); - let recoveryToken = new RecoveryToken({ - email: req.body.email, - token: crypto.randomBytes(32).toString("hex"), - }); - await recoveryToken.save(); - return recoveryToken; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "requestRecoveryToken"; - throw error; - } + try { + // Delete any existing tokens + await RecoveryToken.deleteMany({ email: req.body.email }); + let recoveryToken = new RecoveryToken({ + email: req.body.email, + token: crypto.randomBytes(32).toString("hex"), + }); + await recoveryToken.save(); + return recoveryToken; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "requestRecoveryToken"; + throw error; + } }; const validateRecoveryToken = async (req, res) => { - try { - const candidateToken = req.body.recoveryToken; - const recoveryToken = await RecoveryToken.findOne({ - token: candidateToken, - }); - if (recoveryToken !== null) { - return recoveryToken; - } else { - throw new Error(errorMessages.DB_TOKEN_NOT_FOUND); - } - } catch (error) { - error.service = SERVICE_NAME; - error.method = "validateRecoveryToken"; - throw error; - } + try { + const candidateToken = req.body.recoveryToken; + const recoveryToken = await RecoveryToken.findOne({ + token: candidateToken, + }); + if (recoveryToken !== null) { + return recoveryToken; + } else { + throw new Error(errorMessages.DB_TOKEN_NOT_FOUND); + } + } catch (error) { + error.service = SERVICE_NAME; + error.method = "validateRecoveryToken"; + throw error; + } }; const resetPassword = async (req, res) => { - try { - const newPassword = req.body.password; + try { + const newPassword = req.body.password; - // Validate token again - const recoveryToken = await validateRecoveryToken(req, res); - const user = await UserModel.findOne({ email: recoveryToken.email }); + // Validate token again + const recoveryToken = await validateRecoveryToken(req, res); + const user = await UserModel.findOne({ email: recoveryToken.email }); - const match = await user.comparePassword(newPassword); - if (match === true) { - throw new Error(errorMessages.DB_RESET_PASSWORD_BAD_MATCH); - } + const match = await user.comparePassword(newPassword); + if (match === true) { + throw new Error(errorMessages.DB_RESET_PASSWORD_BAD_MATCH); + } - if (user !== null) { - user.password = newPassword; - await user.save(); - await RecoveryToken.deleteMany({ email: recoveryToken.email }); - // Fetch the user again without the password - const userWithoutPassword = await UserModel.findOne({ - email: recoveryToken.email, - }) - .select("-password") - .select("-profileImage"); - return userWithoutPassword; - } else { - throw new Error(errorMessages.DB_USER_NOT_FOUND); - } - } catch (error) { - error.service = SERVICE_NAME; - error.method = "resetPassword"; - throw error; - } + if (user !== null) { + user.password = newPassword; + await user.save(); + await RecoveryToken.deleteMany({ email: recoveryToken.email }); + // Fetch the user again without the password + const userWithoutPassword = await UserModel.findOne({ + email: recoveryToken.email, + }) + .select("-password") + .select("-profileImage"); + return userWithoutPassword; + } else { + throw new Error(errorMessages.DB_USER_NOT_FOUND); + } + } catch (error) { + error.service = SERVICE_NAME; + error.method = "resetPassword"; + throw error; + } }; export { requestRecoveryToken, validateRecoveryToken, resetPassword }; diff --git a/Server/db/mongo/modules/settingsModule.js b/Server/db/mongo/modules/settingsModule.js index 70dc16175..3b5f68e19 100644 --- a/Server/db/mongo/modules/settingsModule.js +++ b/Server/db/mongo/modules/settingsModule.js @@ -2,29 +2,29 @@ import AppSettings from "../../models/AppSettings.js"; const SERVICE_NAME = "SettingsModule"; const getAppSettings = async () => { - try { - const settings = AppSettings.findOne(); - return settings; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getSettings"; - throw error; - } + try { + const settings = AppSettings.findOne(); + return settings; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getSettings"; + throw error; + } }; const updateAppSettings = async (newSettings) => { - try { - const settings = await AppSettings.findOneAndUpdate( - {}, - { $set: newSettings }, - { new: true } - ); - return settings; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "updateAppSettings"; - throw error; - } + try { + const settings = await AppSettings.findOneAndUpdate( + {}, + { $set: newSettings }, + { new: true } + ); + return settings; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "updateAppSettings"; + throw error; + } }; export { getAppSettings, updateAppSettings }; diff --git a/Server/db/mongo/modules/userModule.js b/Server/db/mongo/modules/userModule.js index 689aceb30..7afe04763 100644 --- a/Server/db/mongo/modules/userModule.js +++ b/Server/db/mongo/modules/userModule.js @@ -16,42 +16,42 @@ const SERVICE_NAME = "userModule"; * @throws {Error} */ const insertUser = async (userData, imageFile) => { - try { - if (imageFile) { - // 1. Save the full size image - userData.profileImage = { - data: imageFile.buffer, - contentType: imageFile.mimetype, - }; + try { + if (imageFile) { + // 1. Save the full size image + userData.profileImage = { + data: imageFile.buffer, + contentType: imageFile.mimetype, + }; - // 2. Get the avatar sized image - const avatar = await GenerateAvatarImage(imageFile); - userData.avatarImage = avatar; - } + // 2. Get the avatar sized image + const avatar = await GenerateAvatarImage(imageFile); + userData.avatarImage = avatar; + } - // Handle creating team if superadmin - if (userData.role.includes("superadmin")) { - const team = new TeamModel({ - email: userData.email, - }); - userData.teamId = team._id; - userData.checkTTL = 60 * 60 * 24 * 30; - await team.save(); - } + // Handle creating team if superadmin + if (userData.role.includes("superadmin")) { + const team = new TeamModel({ + email: userData.email, + }); + userData.teamId = team._id; + userData.checkTTL = 60 * 60 * 24 * 30; + await team.save(); + } - const newUser = new UserModel(userData); - await newUser.save(); - return await UserModel.findOne({ _id: newUser._id }) - .select("-password") - .select("-profileImage"); // .select() doesn't work with create, need to save then find - } catch (error) { - if (error.code === DUPLICATE_KEY_CODE) { - error.message = errorMessages.DB_USER_EXISTS; - } - error.service = SERVICE_NAME; - error.method = "insertUser"; - throw error; - } + const newUser = new UserModel(userData); + await newUser.save(); + return await UserModel.findOne({ _id: newUser._id }) + .select("-password") + .select("-profileImage"); // .select() doesn't work with create, need to save then find + } catch (error) { + if (error.code === DUPLICATE_KEY_CODE) { + error.message = errorMessages.DB_USER_EXISTS; + } + error.service = SERVICE_NAME; + error.method = "insertUser"; + throw error; + } }; /** @@ -66,22 +66,20 @@ const insertUser = async (userData, imageFile) => { * @throws {Error} */ const getUserByEmail = async (email) => { - try { - // Need the password to be able to compare, removed .select() - // We can strip the hash before returing the user - const user = await UserModel.findOne({ email: email }).select( - "-profileImage" - ); - if (user) { - return user; - } else { - throw new Error(errorMessages.DB_USER_NOT_FOUND); - } - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getUserByEmail"; - throw error; - } + try { + // Need the password to be able to compare, removed .select() + // We can strip the hash before returing the user + const user = await UserModel.findOne({ email: email }).select("-profileImage"); + if (user) { + return user; + } else { + throw new Error(errorMessages.DB_USER_NOT_FOUND); + } + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getUserByEmail"; + throw error; + } }; /** @@ -94,45 +92,45 @@ const getUserByEmail = async (email) => { */ const updateUser = async (req, res) => { - const candidateUserId = req.params.userId; - try { - const candidateUser = { ...req.body }; - // ****************************************** - // Handle profile image - // ****************************************** + const candidateUserId = req.params.userId; + try { + const candidateUser = { ...req.body }; + // ****************************************** + // Handle profile image + // ****************************************** - if (ParseBoolean(candidateUser.deleteProfileImage) === true) { - candidateUser.profileImage = null; - candidateUser.avatarImage = null; - } else if (req.file) { - // 1. Save the full size image - candidateUser.profileImage = { - data: req.file.buffer, - contentType: req.file.mimetype, - }; + if (ParseBoolean(candidateUser.deleteProfileImage) === true) { + candidateUser.profileImage = null; + candidateUser.avatarImage = null; + } else if (req.file) { + // 1. Save the full size image + candidateUser.profileImage = { + data: req.file.buffer, + contentType: req.file.mimetype, + }; - // 2. Get the avaatar sized image - const avatar = await GenerateAvatarImage(req.file); - candidateUser.avatarImage = avatar; - } + // 2. Get the avaatar sized image + const avatar = await GenerateAvatarImage(req.file); + candidateUser.avatarImage = avatar; + } - // ****************************************** - // End handling profile image - // ****************************************** + // ****************************************** + // End handling profile image + // ****************************************** - const updatedUser = await UserModel.findByIdAndUpdate( - candidateUserId, - candidateUser, - { new: true } // Returns updated user instead of pre-update user - ) - .select("-password") - .select("-profileImage"); - return updatedUser; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "updateUser"; - throw error; - } + const updatedUser = await UserModel.findByIdAndUpdate( + candidateUserId, + candidateUser, + { new: true } // Returns updated user instead of pre-update user + ) + .select("-password") + .select("-profileImage"); + return updatedUser; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "updateUser"; + throw error; + } }; /** @@ -144,17 +142,17 @@ const updateUser = async (req, res) => { * @throws {Error} */ const deleteUser = async (userId) => { - try { - const deletedUser = await UserModel.findByIdAndDelete(userId); - if (!deletedUser) { - throw new Error(errorMessages.DB_USER_NOT_FOUND); - } - return deletedUser; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteUser"; - throw error; - } + try { + const deletedUser = await UserModel.findByIdAndDelete(userId); + if (!deletedUser) { + throw new Error(errorMessages.DB_USER_NOT_FOUND); + } + return deletedUser; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "deleteUser"; + throw error; + } }; /** @@ -165,56 +163,54 @@ const deleteUser = async (userId) => { * @throws {Error} */ const deleteTeam = async (teamId) => { - try { - await TeamModel.findByIdAndDelete(teamId); - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteTeam"; - throw error; - } + try { + await TeamModel.findByIdAndDelete(teamId); + } catch (error) { + error.service = SERVICE_NAME; + error.method = "deleteTeam"; + throw error; + } }; const deleteAllOtherUsers = async () => { - try { - await UserModel.deleteMany({ role: { $ne: "superadmin" } }); - } catch (error) { - error.service = SERVICE_NAME; - error.method = "deleteAllOtherUsers"; - throw error; - } + try { + await UserModel.deleteMany({ role: { $ne: "superadmin" } }); + } catch (error) { + error.service = SERVICE_NAME; + error.method = "deleteAllOtherUsers"; + throw error; + } }; const getAllUsers = async (req, res) => { - try { - const users = await UserModel.find() - .select("-password") - .select("-profileImage"); - return users; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "getAllUsers"; - throw error; - } + try { + const users = await UserModel.find().select("-password").select("-profileImage"); + return users; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getAllUsers"; + throw error; + } }; const logoutUser = async (userId) => { - try { - await UserModel.updateOne({ _id: userId }, { $unset: { authToken: null } }); - return true; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "logoutUser"; - throw error; - } + try { + await UserModel.updateOne({ _id: userId }, { $unset: { authToken: null } }); + return true; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "logoutUser"; + throw error; + } }; export { - insertUser, - getUserByEmail, - updateUser, - deleteUser, - deleteTeam, - deleteAllOtherUsers, - getAllUsers, - logoutUser, + insertUser, + getUserByEmail, + updateUser, + deleteUser, + deleteTeam, + deleteAllOtherUsers, + getAllUsers, + logoutUser, }; diff --git a/Server/index.js b/Server/index.js index 7f25f1141..68a6c354a 100644 --- a/Server/index.js +++ b/Server/index.js @@ -20,11 +20,25 @@ import { fileURLToPath } from "url"; import { connectDbAndRunServer } from "./configs/db.js"; import queueRouter from "./routes/queueRoute.js"; import JobQueue from "./service/jobQueue.js"; + +//Network service and dependencies import NetworkService from "./service/networkService.js"; +import axios from "axios"; +import ping from "ping"; +import http from "http"; + +// Email service and dependencies import EmailService from "./service/emailService.js"; +import nodemailer from "nodemailer"; +import pkg from "handlebars"; +const { compile } = pkg; +import mjml2html from "mjml"; + +// Settings Service and dependencies import SettingsService from "./service/settingsService.js"; +import AppSettings from "../db/models/AppSettings.js"; + import db from "./db/mongo/MongoDB.js"; -import { fetchMonitorCertificate } from "./controllers/controllerUtils.js"; const SERVICE_NAME = "Server"; let cleaningUp = false; @@ -130,11 +144,19 @@ const startApp = async () => { // Create services await connectDbAndRunServer(app, db); - const settingsService = new SettingsService(); + const settingsService = new SettingsService(AppSettings); await settingsService.loadSettings(); - const emailService = new EmailService(settingsService); - const networkService = new NetworkService(db, emailService); + const emailService = new EmailService( + settingsService, + fs, + path, + compile, + mjml2html, + nodemailer, + logger + ); + const networkService = new NetworkService(db, emailService, axios, ping, logger, http); const jobQueue = await JobQueue.createJobQueue(db, networkService, settingsService); const cleanup = async () => { diff --git a/Server/middleware/handleErrors.js b/Server/middleware/handleErrors.js index c59967382..51b880878 100644 --- a/Server/middleware/handleErrors.js +++ b/Server/middleware/handleErrors.js @@ -2,14 +2,14 @@ import logger from "../utils/logger.js"; import { errorMessages } from "../utils/messages.js"; const handleErrors = (error, req, res, next) => { - const status = error.status || 500; - const message = error.message || errorMessages.FRIENDLY_ERROR; - const service = error.service || errorMessages.UNKNOWN_SERVICE; - logger.error(error.message, { - service: service, - method: error.method, - }); - res.status(status).json({ success: false, msg: message }); + const status = error.status || 500; + const message = error.message || errorMessages.FRIENDLY_ERROR; + const service = error.service || errorMessages.UNKNOWN_SERVICE; + logger.error(error.message, { + service: service, + method: error.method, + }); + res.status(status).json({ success: false, msg: message }); }; export { handleErrors }; diff --git a/Server/middleware/isAllowed.js b/Server/middleware/isAllowed.js index a48afb337..4aa1a0d81 100644 --- a/Server/middleware/isAllowed.js +++ b/Server/middleware/isAllowed.js @@ -4,52 +4,52 @@ const SERVICE_NAME = "allowedRoles"; import { errorMessages } from "../utils/messages.js"; const isAllowed = (allowedRoles) => { - return (req, res, next) => { - const token = req.headers["authorization"]; + return (req, res, next) => { + const token = req.headers["authorization"]; - // If no token is pressent, return an error - if (!token) { - const error = new Error(errorMessages.NO_AUTH_TOKEN); - error.status = 401; - error.service = SERVICE_NAME; - next(error); - return; - } + // If no token is pressent, return an error + if (!token) { + const error = new Error(errorMessages.NO_AUTH_TOKEN); + error.status = 401; + error.service = SERVICE_NAME; + next(error); + return; + } - // If the token is improperly formatted, return an error - if (!token.startsWith(TOKEN_PREFIX)) { - const error = new Error(errorMessages.INVALID_AUTH_TOKEN); - error.status = 400; - error.service = SERVICE_NAME; - next(error); - return; - } - // Parse the token - try { - const parsedToken = token.slice(TOKEN_PREFIX.length, token.length); - const { jwtSecret } = req.settingsService.getSettings(); - var decoded = jwt.verify(parsedToken, jwtSecret); - const userRoles = decoded.role; + // If the token is improperly formatted, return an error + if (!token.startsWith(TOKEN_PREFIX)) { + const error = new Error(errorMessages.INVALID_AUTH_TOKEN); + error.status = 400; + error.service = SERVICE_NAME; + next(error); + return; + } + // Parse the token + try { + const parsedToken = token.slice(TOKEN_PREFIX.length, token.length); + const { jwtSecret } = req.settingsService.getSettings(); + var decoded = jwt.verify(parsedToken, jwtSecret); + const userRoles = decoded.role; - // Check if the user has the required role - if (userRoles.some((role) => allowedRoles.includes(role))) { - next(); - return; - } else { - const error = new Error(errorMessages.INSUFFICIENT_PERMISSIONS); - error.status = 401; - error.service = SERVICE_NAME; - next(error); - return; - } - } catch (error) { - error.status = 401; - error.method = "isAllowed"; - error.service = SERVICE_NAME; - next(error); - return; - } - }; + // Check if the user has the required role + if (userRoles.some((role) => allowedRoles.includes(role))) { + next(); + return; + } else { + const error = new Error(errorMessages.INSUFFICIENT_PERMISSIONS); + error.status = 401; + error.service = SERVICE_NAME; + next(error); + return; + } + } catch (error) { + error.status = 401; + error.method = "isAllowed"; + error.service = SERVICE_NAME; + next(error); + return; + } + }; }; export { isAllowed }; diff --git a/Server/middleware/verifyJWT.js b/Server/middleware/verifyJWT.js index 23b79e2c8..cb52ef297 100644 --- a/Server/middleware/verifyJWT.js +++ b/Server/middleware/verifyJWT.js @@ -13,82 +13,80 @@ const TOKEN_PREFIX = "Bearer "; * @returns {express.Response} */ const verifyJWT = (req, res, next) => { - const token = req.headers["authorization"]; - // Make sure a token is provided - if (!token) { - const error = new Error(errorMessages.NO_AUTH_TOKEN); - error.status = 401; - error.service = SERVICE_NAME; - next(error); - return; - } - // Make sure it is properly formatted - if (!token.startsWith(TOKEN_PREFIX)) { - const error = new Error(errorMessages.INVALID_AUTH_TOKEN); // Instantiate a new Error object for improperly formatted token - error.status = 400; - error.service = SERVICE_NAME; - error.method = "verifyJWT"; - next(error); - return; - } + const token = req.headers["authorization"]; + // Make sure a token is provided + if (!token) { + const error = new Error(errorMessages.NO_AUTH_TOKEN); + error.status = 401; + error.service = SERVICE_NAME; + next(error); + return; + } + // Make sure it is properly formatted + if (!token.startsWith(TOKEN_PREFIX)) { + const error = new Error(errorMessages.INVALID_AUTH_TOKEN); // Instantiate a new Error object for improperly formatted token + error.status = 400; + error.service = SERVICE_NAME; + error.method = "verifyJWT"; + next(error); + return; + } - const parsedToken = token.slice(TOKEN_PREFIX.length, token.length); - // Verify the token's authenticity - const { jwtSecret } = req.settingsService.getSettings(); - jwt.verify(parsedToken, jwtSecret, (err, decoded) => { - if (err) { - if (err.name === "TokenExpiredError") { - // token has expired - handleExpiredJwtToken(req, res, next); - } - else { - // Invalid token (signature or token altered or other issue) - const errorMessage = errorMessages.INVALID_AUTH_TOKEN; - return res.status(401).json({ success: false, msg: errorMessage }); - } - } - else { - // Token is valid and not expired, carry on with request, Add the decoded payload to the request - req.user = decoded; - next(); - } - }); + const parsedToken = token.slice(TOKEN_PREFIX.length, token.length); + // Verify the token's authenticity + const { jwtSecret } = req.settingsService.getSettings(); + jwt.verify(parsedToken, jwtSecret, (err, decoded) => { + if (err) { + if (err.name === "TokenExpiredError") { + // token has expired + handleExpiredJwtToken(req, res, next); + } else { + // Invalid token (signature or token altered or other issue) + const errorMessage = errorMessages.INVALID_AUTH_TOKEN; + return res.status(401).json({ success: false, msg: errorMessage }); + } + } else { + // Token is valid and not expired, carry on with request, Add the decoded payload to the request + req.user = decoded; + next(); + } + }); }; function handleExpiredJwtToken(req, res, next) { - // check for refreshToken - const refreshToken = req.headers["x-refresh-token"]; + // check for refreshToken + const refreshToken = req.headers["x-refresh-token"]; - if (!refreshToken) { - // No refresh token provided - const error = new Error(errorMessages.NO_REFRESH_TOKEN); - error.status = 401; - error.service = SERVICE_NAME; - error.method = "handleExpiredJwtToken"; - return next(error); - } + if (!refreshToken) { + // No refresh token provided + const error = new Error(errorMessages.NO_REFRESH_TOKEN); + error.status = 401; + error.service = SERVICE_NAME; + error.method = "handleExpiredJwtToken"; + return next(error); + } - // Verify refresh token - const { refreshTokenSecret } = req.settingsService.getSettings(); - jwt.verify(refreshToken, refreshTokenSecret, (refreshErr, refreshDecoded) => { - if (refreshErr) { - // Invalid or expired refresh token, trigger logout - const errorMessage = - refreshErr.name === "TokenExpiredError" - ? errorMessages.EXPIRED_REFRESH_TOKEN - : errorMessages.INVALID_REFRESH_TOKEN; - const error = new Error(errorMessage); - error.status = 401; - error.service = SERVICE_NAME; - return next(error); - } + // Verify refresh token + const { refreshTokenSecret } = req.settingsService.getSettings(); + jwt.verify(refreshToken, refreshTokenSecret, (refreshErr, refreshDecoded) => { + if (refreshErr) { + // Invalid or expired refresh token, trigger logout + const errorMessage = + refreshErr.name === "TokenExpiredError" + ? errorMessages.EXPIRED_REFRESH_TOKEN + : errorMessages.INVALID_REFRESH_TOKEN; + const error = new Error(errorMessage); + error.status = 401; + error.service = SERVICE_NAME; + return next(error); + } - // Refresh token is valid and unexpired, request for new access token - res.status(403).json({ - success: false, - msg: errorMessages.REQUEST_NEW_ACCESS_TOKEN, - }); - }); + // Refresh token is valid and unexpired, request for new access token + res.status(403).json({ + success: false, + msg: errorMessages.REQUEST_NEW_ACCESS_TOKEN, + }); + }); } -export { verifyJWT }; \ No newline at end of file +export { verifyJWT }; diff --git a/Server/middleware/verifyOwnership.js b/Server/middleware/verifyOwnership.js index 2b656568b..37c19b42b 100644 --- a/Server/middleware/verifyOwnership.js +++ b/Server/middleware/verifyOwnership.js @@ -3,47 +3,47 @@ import { errorMessages } from "../utils/messages.js"; const SERVICE_NAME = "verifyOwnership"; const verifyOwnership = (Model, paramName) => { - return async (req, res, next) => { - const userId = req.user._id; - const documentId = req.params[paramName]; - try { - const doc = await Model.findById(documentId); - //If the document is not found, return a 404 error - if (!doc) { - logger.error(errorMessages.VERIFY_OWNER_NOT_FOUND, { - service: SERVICE_NAME, - }); - const error = new Error(errorMessages.VERIFY_OWNER_NOT_FOUND); - error.status = 404; - throw error; - } + return async (req, res, next) => { + const userId = req.user._id; + const documentId = req.params[paramName]; + try { + const doc = await Model.findById(documentId); + //If the document is not found, return a 404 error + if (!doc) { + logger.error(errorMessages.VERIFY_OWNER_NOT_FOUND, { + service: SERVICE_NAME, + }); + const error = new Error(errorMessages.VERIFY_OWNER_NOT_FOUND); + error.status = 404; + throw error; + } - // Special case for User model, as it will not have a `userId` field as other docs will - if (Model.modelName === "User") { - if (userId.toString() !== doc._id.toString()) { - const error = new Error(errorMessages.VERIFY_OWNER_UNAUTHORIZED); - error.status = 403; - throw error; - } - next(); - return; - } + // Special case for User model, as it will not have a `userId` field as other docs will + if (Model.modelName === "User") { + if (userId.toString() !== doc._id.toString()) { + const error = new Error(errorMessages.VERIFY_OWNER_UNAUTHORIZED); + error.status = 403; + throw error; + } + next(); + return; + } - // If the userID does not match the document's userID, return a 403 error - if (userId.toString() !== doc.userId.toString()) { - const error = new Error(errorMessages.VERIFY_OWNER_UNAUTHORIZED); - error.status = 403; - throw error; - } - next(); - return; - } catch (error) { - error.service = SERVICE_NAME; - error.method = "verifyOwnership"; - next(error); - return; - } - }; + // If the userID does not match the document's userID, return a 403 error + if (userId.toString() !== doc.userId.toString()) { + const error = new Error(errorMessages.VERIFY_OWNER_UNAUTHORIZED); + error.status = 403; + throw error; + } + next(); + return; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "verifyOwnership"; + next(error); + return; + } + }; }; export { verifyOwnership }; diff --git a/Server/middleware/verifySuperAdmin.js b/Server/middleware/verifySuperAdmin.js index cf8774c31..073526791 100644 --- a/Server/middleware/verifySuperAdmin.js +++ b/Server/middleware/verifySuperAdmin.js @@ -12,49 +12,47 @@ const { errorMessages } = require("../utils/messages"); * @returns {express.Response} */ const verifySuperAdmin = (req, res, next) => { - const token = req.headers["authorization"]; - // Make sure a token is provided - if (!token) { - const error = new Error(errorMessages.NO_AUTH_TOKEN); - error.status = 401; - error.service = SERVICE_NAME; - next(error); - return; - } - // Make sure it is properly formatted - if (!token.startsWith(TOKEN_PREFIX)) { - const error = new Error(errorMessages.INVALID_AUTH_TOKEN); // Instantiate a new Error object for improperly formatted token - error.status = 400; - error.service = SERVICE_NAME; - error.method = "verifySuperAdmin"; - next(error); - return; - } + const token = req.headers["authorization"]; + // Make sure a token is provided + if (!token) { + const error = new Error(errorMessages.NO_AUTH_TOKEN); + error.status = 401; + error.service = SERVICE_NAME; + next(error); + return; + } + // Make sure it is properly formatted + if (!token.startsWith(TOKEN_PREFIX)) { + const error = new Error(errorMessages.INVALID_AUTH_TOKEN); // Instantiate a new Error object for improperly formatted token + error.status = 400; + error.service = SERVICE_NAME; + error.method = "verifySuperAdmin"; + next(error); + return; + } - const parsedToken = token.slice(TOKEN_PREFIX.length, token.length); - // verify admin role is present - const { jwtSecret } = req.settingsService.getSettings(); + const parsedToken = token.slice(TOKEN_PREFIX.length, token.length); + // verify admin role is present + const { jwtSecret } = req.settingsService.getSettings(); - jwt.verify(parsedToken, jwtSecret, (err, decoded) => { - if (err) { - logger.error(errorMessages.INVALID_AUTH_TOKEN, { - service: SERVICE_NAME, - }); - return res - .status(401) - .json({ success: false, msg: errorMessages.INVALID_AUTH_TOKEN }); - } + jwt.verify(parsedToken, jwtSecret, (err, decoded) => { + if (err) { + logger.error(errorMessages.INVALID_AUTH_TOKEN, { + service: SERVICE_NAME, + }); + return res + .status(401) + .json({ success: false, msg: errorMessages.INVALID_AUTH_TOKEN }); + } - if (decoded.role.includes("superadmin") === false) { - logger.error(errorMessages.INVALID_AUTH_TOKEN, { - service: SERVICE_NAME, - }); - return res - .status(401) - .json({ success: false, msg: errorMessages.UNAUTHORIZED }); - } - next(); - }); + if (decoded.role.includes("superadmin") === false) { + logger.error(errorMessages.INVALID_AUTH_TOKEN, { + service: SERVICE_NAME, + }); + return res.status(401).json({ success: false, msg: errorMessages.UNAUTHORIZED }); + } + next(); + }); }; module.exports = { verifySuperAdmin }; diff --git a/Server/openapi.json b/Server/openapi.json index 6dc9ef360..9c3e6d062 100644 --- a/Server/openapi.json +++ b/Server/openapi.json @@ -1,2347 +1,2576 @@ { - "openapi": "3.1.0", - "info": { - "title": "BlueWave Uptime", - "summary": "BlueWave Uptime OpenAPI Specifications", - "description": "BlueWave Uptime is an open source server monitoring application used to track the operational status and performance of servers and websites. It regularly checks whether a server/website is accessible and performs optimally, providing real-time alerts and reports on the monitored services' availability, downtime, and response time.", - "contact": { - "name": "API Support", - "url": "mailto:support@bluewavelabs.ca", - "email": "support@bluewavelabs.ca" - }, - "license": { - "name": "AGPLv3", - "url": "https://github.com/bluewave-labs/bluewave-uptime/tree/HEAD/LICENSE" - }, - "version": "1.0" - }, - "servers": [ - { - "url": "http://localhost:{PORT}/{API_PATH}", - "description": "Local Development Server", - "variables": { - "PORT": { - "description": "API Port", - "enum": ["5000"], - "default": "5000" - }, - "API_PATH": { - "description": "API Base Path", - "enum": ["api/v1"], - "default": "api/v1" - } - } - }, - { - "url": "http://localhost/{API_PATH}", - "description": "Distribution Local Development Server", - "variables": { - "API_PATH": { - "description": "API Base Path", - "enum": ["api/v1"], - "default": "api/v1" - } - } - }, - { - "url": "https://uptime-demo.bluewavelabs.ca/{API_PATH}", - "description": "Bluewave Demo Server", - "variables": { - "PORT": { - "description": "API Port", - "enum": ["5000"], - "default": "5000" - }, - "API_PATH": { - "description": "API Base Path", - "enum": ["api/v1"], - "default": "api/v1" - } - } - } - ], - "tags": [ - { - "name": "auth", - "description": "Authentication" - }, - { - "name": "invite", - "description": "Invite" - }, - { - "name": "monitors", - "description": "Monitors" - }, - { - "name": "checks", - "description": "Checks" - }, - { - "name": "maintenance-window", - "description": "Maintenance window" - }, - { - "name": "queue", - "description": "Queue" - } - ], - "paths": { - "/auth/register": { - "post": { - "tags": ["auth"], - "description": "Register a new user", - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "required": [ - "firstName", - "lastName", - "email", - "password", - "role", - "teamId" - ], - "properties": { - "firstName": { - "type": "string" - }, - "lastName": { - "type": "string" - }, - "email": { - "type": "string", - "format": "email" - }, - "password": { - "type": "string", - "format": "password" - }, - "profileImage": { - "type": "file", - "format": "file" - }, - "role": { - "type": "array", - "enum": [["user"], ["admin"], ["superadmin"], ["Demo"]], - "default": ["superadmin"] - }, - "teamId": { - "type": "string", - "format": "uuid" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - } - } - }, - "/auth/login": { - "post": { - "tags": ["auth"], - "description": "Login with credentials", - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["email", "password"], - "properties": { - "email": { - "type": "string", - "format": "email" - }, - "password": { - "type": "string", - "format": "password" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - } - } - }, - "/auth/refresh": { - "post": { - "tags": ["auth"], - "description": "Generates a new auth token if the refresh token is valid.", - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": {} - } - } - }, - "required": false - }, - "parameters": [ - { - "name": "x-refresh-token", - "in": "header", - "description": "Refresh token required to generate a new auth token.", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "authorization", - "in": "header", - "description": "Old access token, used to extract payload).", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "New access token generated.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "401": { - "description": "Unauthorized or invalid refresh token.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - } - } - }, - "/auth/user/{userId}": { - "put": { - "tags": ["auth"], - "description": "Change user information", - "parameters": [ - { - "name": "userId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UserUpdateRequest" - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - }, - "delete": { - "tags": ["auth"], - "description": "Delete user", - "parameters": [ - { - "name": "userId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/auth/users/superadmin": { - "get": { - "tags": ["auth"], - "description": "Checks to see if an admin account exists", - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/auth/users": { - "get": { - "tags": ["auth"], - "description": "Get all users", - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/auth/recovery/request": { - "post": { - "tags": ["auth"], - "description": "Request a recovery token", - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["email"], - "properties": { - "email": { - "type": "string", - "format": "email" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - } - } - }, - "/auth/recovery/validate": { - "post": { - "tags": ["auth"], - "description": "Validate recovery token", - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["recoveryToken"], - "properties": { - "recoveryToken": { - "type": "string" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - } - } - }, - "/auth/recovery/reset": { - "post": { - "tags": ["auth"], - "description": "Password reset", - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["recoveryToken", "password"], - "properties": { - "recoveryToken": { - "type": "string" - }, - "password": { - "type": "string" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - } - } - }, - "/invite": { - "post": { - "tags": ["invite"], - "description": "Request an invitation", - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["email", "role"], - "properties": { - "email": { - "type": "string" - }, - "role": { - "type": "array" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/invite/verify": { - "post": { - "tags": ["invite"], - "description": "Request an invitation", - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "required": ["token"], - "properties": { - "token": { - "type": "string" - } - } - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/monitors": { - "get": { - "tags": ["monitors"], - "description": "Get all monitors", - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - }, - "post": { - "tags": ["monitors"], - "description": "Create a new monitor", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CreateMonitorBody" - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - }, - "delete": { - "tags": ["monitors"], - "description": "Delete all monitors", - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/monitors/{monitorId}": { - "get": { - "tags": ["monitors"], - "description": "Get monitor by id", - "parameters": [ - { - "name": "monitorId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - }, - "put": { - "tags": ["monitors"], - "description": "Update monitor by id", - "parameters": [ - { - "name": "monitorId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UpdateMonitorBody" - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - }, - "delete": { - "tags": ["monitors"], - "description": "Delete monitor by id", - "parameters": [ - { - "name": "monitorId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/monitors/stats/{monitorId}": { - "get": { - "tags": ["monitors"], - "description": "Get monitor stats", - "parameters": [ - { - "name": "monitorId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/monitors/certificate/{monitorId}": { - "get": { - "tags": ["monitors"], - "description": "Get monitor certificate", - "parameters": [ - { - "name": "monitorId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/monitors/team/summary/{teamId}": { - "get": { - "tags": ["monitors"], - "description": "Get monitors and summary by teamId", - "parameters": [ - { - "name": "teamId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "type", - "in": "query", - "required": false, - "schema": { - "type": "array", - "enum": ["http", "ping", "pagespeed"] - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/monitors/team/{teamId}": { - "get": { - "tags": ["monitors"], - "description": "Get monitors by teamId", - "parameters": [ - { - "name": "teamId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "status", - "description": "Status of monitor, true for up, false for down", - "in": "query", - "required": false, - "schema": { - "type": "boolean" - } - }, - { - "name": "checkOrder", - "description": "Order of checks", - "in": "query", - "required": false, - "schema": { - "type": "string", - "enum": ["asc", "desc"] - } - }, - { - "name": "limit", - "description": "Number of checks to return with monitor", - "in": "query", - "required": false, - "schema": { - "type": "integer" - } - }, - { - "name": "type", - "description": "Type of monitor", - "in": "query", - "required": false, - "schema": { - "type": "string", - "enum": ["http", "ping", "pagespeed"] - } - }, - { - "name": "page", - "in": "query", - "required": false, - "schema": { - "type": "integer" - } - }, - { - "name": "rowsPerPage", - "in": "query", - "required": false, - "schema": { - "type": "integer" - } - }, - { - "name": "filter", - "description": "Value to filter by", - "in": "query", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "field", - "description": "Field to filter on", - "in": "query", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "order", - "description": "Sort order of results", - "in": "query", - "required": false, - "schema": { - "type": "string", - "enum": ["http", "ping", "pagespeed"] - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/monitors/pause/{monitorId}": { - "post": { - "tags": ["monitors"], - "description": "Pause monitor", - "parameters": [ - { - "name": "monitorId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/monitors/demo": { - "post": { - "tags": ["monitors"], - "description": "Create a demo monitor", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CreateMonitorBody" - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/checks/{monitorId}": { - "get": { - "tags": ["checks"], - "description": "Get all checks for a monitor", - "parameters": [ - { - "name": "monitorId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - }, - "post": { - "tags": ["checks"], - "description": "Create a new check", - "parameters": [ - { - "name": "monitorId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CreateCheckBody" - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - }, - "delete": { - "tags": ["checks"], - "description": "Delete all checks for a monitor", - "parameters": [ - { - "name": "monitorId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/checks/team/{teamId}": { - "get": { - "tags": ["checks"], - "description": "Get all checks for a team", - "parameters": [ - { - "name": "teamId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - }, - "delete": { - "tags": ["checks"], - "description": "Delete all checks for a team", - "parameters": [ - { - "name": "teamId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/checks/team/ttl": { - "put": { - "tags": ["checks"], - "description": "Update check TTL", - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UpdateCheckTTLBody" - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/maintenance-window/monitor/{monitorId}": { - "get": { - "tags": ["maintenance-window"], - "description": "Get maintenance window for monitor", - "parameters": [ - { - "name": "monitorId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - }, - "post": { - "tags": ["maintenance-window"], - "description": "Create maintenance window for monitor", - "parameters": [ - { - "name": "monitorId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CreateMaintenanceWindowBody" - } - } - } - }, - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/maintenance-window/user/{userId}": { - "get": { - "tags": ["maintenance-window"], - "description": "Get maintenance window for user", - "parameters": [ - { - "name": "userId", - "in": "path", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/queue/jobs": { - "get": { - "tags": ["queue"], - "description": "Get all jobs in queue", - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - }, - "post": { - "tags": ["queue"], - "description": "Create a new job. Useful for testing scaling workers", - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/queue/metrics": { - "get": { - "tags": ["queue"], - "description": "Get queue metrics", - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - }, - "/queue/obliterate": { - "post": { - "tags": ["queue"], - "description": "Obliterate job queue", - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/SuccessResponse" - } - } - } - }, - "422": { - "description": "Unprocessable Content", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - }, - "500": { - "description": "Internal Server Error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ErrorResponse" - } - } - } - } - }, - "security": [ - { - "bearerAuth": [] - } - ] - } - } - }, - - "components": { - "securitySchemes": { - "bearerAuth": { - "type": "http", - "scheme": "bearer", - "bearerFormat": "JWT" - } - }, - "schemas": { - "ErrorResponse": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "default": false - }, - "msg": { - "type": "string" - } - } - }, - "SuccessResponse": { - "type": "object", - "properties": { - "success": { - "type": "boolean", - "default": true - }, - "msg": { - "type": "string" - }, - "data": { - "type": "object" - } - } - }, - "UserUpdateRequest": { - "type": "object", - "required": [ - "firstName", - "lastName", - "email", - "password", - "role", - "teamId" - ], - "properties": { - "firstName": { - "type": "string" - }, - "lastName": { - "type": "string" - }, - "password": { - "type": "string", - "format": "password" - }, - "newPassword": { - "type": "string", - "format": "password" - }, - "profileImage": { - "type": "file", - "format": "file" - }, - "role": { - "type": "array", - "enum": [["user"], ["admin"], ["superadmin"], ["Demo"]], - "default": ["superadmin"] - }, - "deleteProfileImage": { - "type": "boolean" - } - } - }, - "CreateMonitorBody": { - "type": "object", - "required": ["userId", "teamId", "name", "description", "type", "url"], - "properties": { - "_id": { - "type": "string" - }, - "userId": { - "type": "string" - }, - "teamId": { - "type": "string" - }, - "name": { - "type": "string" - }, - "description": { - "type": "string" - }, - "type": { - "type": "string", - "enum": ["http", "ping", "pagespeed"] - }, - "url": { - "type": "string" - }, - "isActive": { - "type": "boolean" - }, - "interval": { - "type": "integer" - }, - "notifications": { - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "UpdateMonitorBody": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "description": { - "type": "string" - }, - "interval": { - "type": "integer" - }, - "notifications": { - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "CreateCheckBody": { - "type": "object", - "required": [ - "monitorId", - "status", - "responseTime", - "statusCode", - "message" - ], - "properties": { - "monitorId": { - "type": "string" - }, - "status": { - "type": "boolean" - }, - "responseTime": { - "type": "integer" - }, - "statusCode": { - "type": "integer" - }, - "message": { - "type": "string" - } - } - }, - "UpdateCheckTTLBody": { - "type": "object", - "required": ["ttl"], - "properties": { - "ttl": { - "type": "integer" - } - } - }, - "CreateMaintenanceWindowBody": { - "type": "object", - "required": ["userId", "active", "oneTime", "start", "end"], - "properties": { - "userId": { - "type": "string" - }, - "active": { - "type": "boolean" - }, - "oneTime": { - "type": "boolean" - }, - "start": { - "type": "string", - "format": "date-time" - }, - "end": { - "type": "string", - "format": "date-time" - }, - "expiry": { - "type": "string", - "format": "date-time" - } - } - } - } - } + "openapi": "3.1.0", + "info": { + "title": "BlueWave Uptime", + "summary": "BlueWave Uptime OpenAPI Specifications", + "description": "BlueWave Uptime is an open source server monitoring application used to track the operational status and performance of servers and websites. It regularly checks whether a server/website is accessible and performs optimally, providing real-time alerts and reports on the monitored services' availability, downtime, and response time.", + "contact": { + "name": "API Support", + "url": "mailto:support@bluewavelabs.ca", + "email": "support@bluewavelabs.ca" + }, + "license": { + "name": "AGPLv3", + "url": "https://github.com/bluewave-labs/bluewave-uptime/tree/HEAD/LICENSE" + }, + "version": "1.0" + }, + "servers": [ + { + "url": "http://localhost:{PORT}/{API_PATH}", + "description": "Local Development Server", + "variables": { + "PORT": { + "description": "API Port", + "enum": [ + "5000" + ], + "default": "5000" + }, + "API_PATH": { + "description": "API Base Path", + "enum": [ + "api/v1" + ], + "default": "api/v1" + } + } + }, + { + "url": "http://localhost/{API_PATH}", + "description": "Distribution Local Development Server", + "variables": { + "API_PATH": { + "description": "API Base Path", + "enum": [ + "api/v1" + ], + "default": "api/v1" + } + } + }, + { + "url": "https://uptime-demo.bluewavelabs.ca/{API_PATH}", + "description": "Bluewave Demo Server", + "variables": { + "PORT": { + "description": "API Port", + "enum": [ + "5000" + ], + "default": "5000" + }, + "API_PATH": { + "description": "API Base Path", + "enum": [ + "api/v1" + ], + "default": "api/v1" + } + } + } + ], + "tags": [ + { + "name": "auth", + "description": "Authentication" + }, + { + "name": "invite", + "description": "Invite" + }, + { + "name": "monitors", + "description": "Monitors" + }, + { + "name": "checks", + "description": "Checks" + }, + { + "name": "maintenance-window", + "description": "Maintenance window" + }, + { + "name": "queue", + "description": "Queue" + } + ], + "paths": { + "/auth/register": { + "post": { + "tags": [ + "auth" + ], + "description": "Register a new user", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "firstName", + "lastName", + "email", + "password", + "role", + "teamId" + ], + "properties": { + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "email": { + "type": "string", + "format": "email" + }, + "password": { + "type": "string", + "format": "password" + }, + "profileImage": { + "type": "file", + "format": "file" + }, + "role": { + "type": "array", + "enum": [ + [ + "user" + ], + [ + "admin" + ], + [ + "superadmin" + ], + [ + "Demo" + ] + ], + "default": [ + "superadmin" + ] + }, + "teamId": { + "type": "string", + "format": "uuid" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/auth/login": { + "post": { + "tags": [ + "auth" + ], + "description": "Login with credentials", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "email", + "password" + ], + "properties": { + "email": { + "type": "string", + "format": "email" + }, + "password": { + "type": "string", + "format": "password" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/auth/refresh": { + "post": { + "tags": [ + "auth" + ], + "description": "Generates a new auth token if the refresh token is valid.", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": {} + } + } + }, + "required": false + }, + "parameters": [ + { + "name": "x-refresh-token", + "in": "header", + "description": "Refresh token required to generate a new auth token.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "authorization", + "in": "header", + "description": "Old access token, used to extract payload).", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "New access token generated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "401": { + "description": "Unauthorized or invalid refresh token.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/auth/user/{userId}": { + "put": { + "tags": [ + "auth" + ], + "description": "Change user information", + "parameters": [ + { + "name": "userId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserUpdateRequest" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + }, + "delete": { + "tags": [ + "auth" + ], + "description": "Delete user", + "parameters": [ + { + "name": "userId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/auth/users/superadmin": { + "get": { + "tags": [ + "auth" + ], + "description": "Checks to see if an admin account exists", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/auth/users": { + "get": { + "tags": [ + "auth" + ], + "description": "Get all users", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/auth/recovery/request": { + "post": { + "tags": [ + "auth" + ], + "description": "Request a recovery token", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "email" + ], + "properties": { + "email": { + "type": "string", + "format": "email" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/auth/recovery/validate": { + "post": { + "tags": [ + "auth" + ], + "description": "Validate recovery token", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "recoveryToken" + ], + "properties": { + "recoveryToken": { + "type": "string" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/auth/recovery/reset": { + "post": { + "tags": [ + "auth" + ], + "description": "Password reset", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "recoveryToken", + "password" + ], + "properties": { + "recoveryToken": { + "type": "string" + }, + "password": { + "type": "string" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/invite": { + "post": { + "tags": [ + "invite" + ], + "description": "Request an invitation", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "email", + "role" + ], + "properties": { + "email": { + "type": "string" + }, + "role": { + "type": "array" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/invite/verify": { + "post": { + "tags": [ + "invite" + ], + "description": "Request an invitation", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "token" + ], + "properties": { + "token": { + "type": "string" + } + } + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/monitors": { + "get": { + "tags": [ + "monitors" + ], + "description": "Get all monitors", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + }, + "post": { + "tags": [ + "monitors" + ], + "description": "Create a new monitor", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateMonitorBody" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + }, + "delete": { + "tags": [ + "monitors" + ], + "description": "Delete all monitors", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/monitors/resolution/url": { + "get": { + "tags": [ + "monitors" + ], + "description": "Check DNS resolution for a given URL", + "parameters": [ + { + "name": "monitorURL", + "in": "query", + "required": true, + "schema": { + "type": "string", + "example": "https://example.com" + }, + "description": "The URL to check DNS resolution for" + } + ], + "responses": { + "200": { + "description": "URL resolved successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "400": { + "description": "DNS resolution failed", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/monitors/{monitorId}": { + "get": { + "tags": [ + "monitors" + ], + "description": "Get monitor by id", + "parameters": [ + { + "name": "monitorId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + }, + "put": { + "tags": [ + "monitors" + ], + "description": "Update monitor by id", + "parameters": [ + { + "name": "monitorId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateMonitorBody" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + }, + "delete": { + "tags": [ + "monitors" + ], + "description": "Delete monitor by id", + "parameters": [ + { + "name": "monitorId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/monitors/stats/{monitorId}": { + "get": { + "tags": [ + "monitors" + ], + "description": "Get monitor stats", + "parameters": [ + { + "name": "monitorId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/monitors/certificate/{monitorId}": { + "get": { + "tags": [ + "monitors" + ], + "description": "Get monitor certificate", + "parameters": [ + { + "name": "monitorId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/monitors/team/summary/{teamId}": { + "get": { + "tags": [ + "monitors" + ], + "description": "Get monitors and summary by teamId", + "parameters": [ + { + "name": "teamId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "type", + "in": "query", + "required": false, + "schema": { + "type": "array", + "enum": [ + "http", + "ping", + "pagespeed" + ] + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/monitors/team/{teamId}": { + "get": { + "tags": [ + "monitors" + ], + "description": "Get monitors by teamId", + "parameters": [ + { + "name": "teamId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "status", + "description": "Status of monitor, true for up, false for down", + "in": "query", + "required": false, + "schema": { + "type": "boolean" + } + }, + { + "name": "checkOrder", + "description": "Order of checks", + "in": "query", + "required": false, + "schema": { + "type": "string", + "enum": [ + "asc", + "desc" + ] + } + }, + { + "name": "limit", + "description": "Number of checks to return with monitor", + "in": "query", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "name": "type", + "description": "Type of monitor", + "in": "query", + "required": false, + "schema": { + "type": "string", + "enum": [ + "http", + "ping", + "pagespeed" + ] + } + }, + { + "name": "page", + "in": "query", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "name": "rowsPerPage", + "in": "query", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "name": "filter", + "description": "Value to filter by", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "field", + "description": "Field to filter on", + "in": "query", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "order", + "description": "Sort order of results", + "in": "query", + "required": false, + "schema": { + "type": "string", + "enum": [ + "http", + "ping", + "pagespeed" + ] + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/monitors/pause/{monitorId}": { + "post": { + "tags": [ + "monitors" + ], + "description": "Pause monitor", + "parameters": [ + { + "name": "monitorId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/monitors/demo": { + "post": { + "tags": [ + "monitors" + ], + "description": "Create a demo monitor", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateMonitorBody" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/checks/{monitorId}": { + "get": { + "tags": [ + "checks" + ], + "description": "Get all checks for a monitor", + "parameters": [ + { + "name": "monitorId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + }, + "post": { + "tags": [ + "checks" + ], + "description": "Create a new check", + "parameters": [ + { + "name": "monitorId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateCheckBody" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + }, + "delete": { + "tags": [ + "checks" + ], + "description": "Delete all checks for a monitor", + "parameters": [ + { + "name": "monitorId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/checks/team/{teamId}": { + "get": { + "tags": [ + "checks" + ], + "description": "Get all checks for a team", + "parameters": [ + { + "name": "teamId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + }, + "delete": { + "tags": [ + "checks" + ], + "description": "Delete all checks for a team", + "parameters": [ + { + "name": "teamId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/checks/team/ttl": { + "put": { + "tags": [ + "checks" + ], + "description": "Update check TTL", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateCheckTTLBody" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/maintenance-window/monitor/{monitorId}": { + "get": { + "tags": [ + "maintenance-window" + ], + "description": "Get maintenance window for monitor", + "parameters": [ + { + "name": "monitorId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + }, + "post": { + "tags": [ + "maintenance-window" + ], + "description": "Create maintenance window for monitor", + "parameters": [ + { + "name": "monitorId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateMaintenanceWindowBody" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/maintenance-window/user/{userId}": { + "get": { + "tags": [ + "maintenance-window" + ], + "description": "Get maintenance window for user", + "parameters": [ + { + "name": "userId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/queue/jobs": { + "get": { + "tags": [ + "queue" + ], + "description": "Get all jobs in queue", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + }, + "post": { + "tags": [ + "queue" + ], + "description": "Create a new job. Useful for testing scaling workers", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/queue/metrics": { + "get": { + "tags": [ + "queue" + ], + "description": "Get queue metrics", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "/queue/obliterate": { + "post": { + "tags": [ + "queue" + ], + "description": "Obliterate job queue", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SuccessResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Content", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + } + }, + "components": { + "securitySchemes": { + "bearerAuth": { + "type": "http", + "scheme": "bearer", + "bearerFormat": "JWT" + } + }, + "schemas": { + "ErrorResponse": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "default": false + }, + "msg": { + "type": "string" + } + } + }, + "SuccessResponse": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "default": true + }, + "msg": { + "type": "string" + }, + "data": { + "type": "object" + } + } + }, + "UserUpdateRequest": { + "type": "object", + "required": [ + "firstName", + "lastName", + "email", + "password", + "role", + "teamId" + ], + "properties": { + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "password": { + "type": "string", + "format": "password" + }, + "newPassword": { + "type": "string", + "format": "password" + }, + "profileImage": { + "type": "file", + "format": "file" + }, + "role": { + "type": "array", + "enum": [ + [ + "user" + ], + [ + "admin" + ], + [ + "superadmin" + ], + [ + "Demo" + ] + ], + "default": [ + "superadmin" + ] + }, + "deleteProfileImage": { + "type": "boolean" + } + } + }, + "CreateMonitorBody": { + "type": "object", + "required": [ + "userId", + "teamId", + "name", + "description", + "type", + "url" + ], + "properties": { + "_id": { + "type": "string" + }, + "userId": { + "type": "string" + }, + "teamId": { + "type": "string" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "http", + "ping", + "pagespeed" + ] + }, + "url": { + "type": "string" + }, + "isActive": { + "type": "boolean" + }, + "interval": { + "type": "integer" + }, + "notifications": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "UpdateMonitorBody": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "interval": { + "type": "integer" + }, + "notifications": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "CreateCheckBody": { + "type": "object", + "required": [ + "monitorId", + "status", + "responseTime", + "statusCode", + "message" + ], + "properties": { + "monitorId": { + "type": "string" + }, + "status": { + "type": "boolean" + }, + "responseTime": { + "type": "integer" + }, + "statusCode": { + "type": "integer" + }, + "message": { + "type": "string" + } + } + }, + "UpdateCheckTTLBody": { + "type": "object", + "required": [ + "ttl" + ], + "properties": { + "ttl": { + "type": "integer" + } + } + }, + "CreateMaintenanceWindowBody": { + "type": "object", + "required": [ + "userId", + "active", + "oneTime", + "start", + "end" + ], + "properties": { + "userId": { + "type": "string" + }, + "active": { + "type": "boolean" + }, + "oneTime": { + "type": "boolean" + }, + "start": { + "type": "string", + "format": "date-time" + }, + "end": { + "type": "string", + "format": "date-time" + }, + "expiry": { + "type": "string", + "format": "date-time" + } + } + } + } + } } diff --git a/Server/package-lock.json b/Server/package-lock.json index 51f940003..eb2364064 100644 --- a/Server/package-lock.json +++ b/Server/package-lock.json @@ -27,6 +27,7 @@ "nodemailer": "^6.9.14", "ping": "0.4.4", "sharp": "0.33.4", + "ssl-checker": "2.0.10", "swagger-ui-express": "5.0.1", "winston": "^3.13.0" }, @@ -35,7 +36,7 @@ "chai": "5.1.1", "esm": "3.2.25", "mocha": "10.7.3", - "nodemon": "3.1.0", + "nodemon": "3.1.7", "prettier": "^3.3.3", "sinon": "19.0.2" } @@ -4607,10 +4608,11 @@ } }, "node_modules/nodemon": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.0.tgz", - "integrity": "sha512-xqlktYlDMCepBJd43ZQhjWwMw2obW/JRvkrLxq5RCNcuDDX1DbcPT+qT1IlIIdf+DhnWs90JpTMe+Y5KxOchvA==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.7.tgz", + "integrity": "sha512-hLj7fuMow6f0lbB0cD14Lz2xNjwsyruH251Pk4t/yIitCFJbmY1myuLlHm/q06aST4jg6EgAh74PIBBrRqpVAQ==", "dev": true, + "license": "MIT", "dependencies": { "chokidar": "^3.5.2", "debug": "^4", @@ -4635,12 +4637,13 @@ } }, "node_modules/nodemon/node_modules/debug": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", - "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dev": true, + "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -4652,10 +4655,11 @@ } }, "node_modules/nodemon/node_modules/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 + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" }, "node_modules/nopt": { "version": "5.0.0", @@ -6002,6 +6006,12 @@ "memory-pager": "^1.0.2" } }, + "node_modules/ssl-checker": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/ssl-checker/-/ssl-checker-2.0.10.tgz", + "integrity": "sha512-SS6rrZocToJWHM1p6iVNb583ybB3UqT1fymCHSWuEdXDUqKA6O1D5Fb8KJVmhj3XKXE82IEWcr+idJrc4jUzFQ==", + "license": "MIT" + }, "node_modules/stack-trace": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", diff --git a/Server/package.json b/Server/package.json index a4835fdee..6d31c2165 100644 --- a/Server/package.json +++ b/Server/package.json @@ -30,16 +30,17 @@ "nodemailer": "^6.9.14", "ping": "0.4.4", "sharp": "0.33.4", + "ssl-checker": "2.0.10", "swagger-ui-express": "5.0.1", "winston": "^3.13.0" }, "devDependencies": { - "prettier": "^3.3.3", - "chai": "5.1.1", - "mocha": "10.7.3", "c8": "10.1.2", + "chai": "5.1.1", "esm": "3.2.25", - "nodemon": "3.1.0", + "mocha": "10.7.3", + "nodemon": "3.1.7", + "prettier": "^3.3.3", "sinon": "19.0.2" } } diff --git a/Server/routes/checkRoute.js b/Server/routes/checkRoute.js index 7a29ae5bd..a57b7410d 100644 --- a/Server/routes/checkRoute.js +++ b/Server/routes/checkRoute.js @@ -1,11 +1,11 @@ import { Router } from "express"; import { - createCheck, - getChecks, - deleteChecks, - getTeamChecks, - deleteChecksByTeamId, - updateChecksTTL, + createCheck, + getChecks, + deleteChecks, + getTeamChecks, + deleteChecksByTeamId, + updateChecksTTL, } from "../controllers/checkController.js"; import { verifyOwnership } from "../middleware/verifyOwnership.js"; import { isAllowed } from "../middleware/isAllowed.js"; @@ -15,19 +15,11 @@ const router = Router(); router.get("/:monitorId", getChecks); router.post("/:monitorId", verifyOwnership(Monitor, "monitorId"), createCheck); -router.delete( - "/:monitorId", - verifyOwnership(Monitor, "monitorId"), - deleteChecks -); +router.delete("/:monitorId", verifyOwnership(Monitor, "monitorId"), deleteChecks); router.get("/team/:teamId", getTeamChecks); -router.delete( - "/team/:teamId", - isAllowed(["admin", "superadmin"]), - deleteChecksByTeamId -); +router.delete("/team/:teamId", isAllowed(["admin", "superadmin"]), deleteChecksByTeamId); router.put("/team/ttl", isAllowed(["admin", "superadmin"]), updateChecksTTL); diff --git a/Server/routes/inviteRoute.js b/Server/routes/inviteRoute.js index b64074751..f1f8a20e1 100644 --- a/Server/routes/inviteRoute.js +++ b/Server/routes/inviteRoute.js @@ -2,18 +2,13 @@ import { Router } from "express"; import { verifyJWT } from "../middleware/verifyJWT.js"; import { isAllowed } from "../middleware/isAllowed.js"; import { - issueInvitation, - inviteVerifyController, + issueInvitation, + inviteVerifyController, } from "../controllers/inviteController.js"; const router = Router(); -router.post( - "/", - isAllowed(["admin", "superadmin"]), - verifyJWT, - issueInvitation -); +router.post("/", isAllowed(["admin", "superadmin"]), verifyJWT, issueInvitation); router.post("/verify", issueInvitation); export default router; diff --git a/Server/routes/maintenanceWindowRoute.js b/Server/routes/maintenanceWindowRoute.js index 8db5b670d..e21e4b9ec 100644 --- a/Server/routes/maintenanceWindowRoute.js +++ b/Server/routes/maintenanceWindowRoute.js @@ -1,11 +1,11 @@ import { Router } from "express"; import { - createMaintenanceWindows, - getMaintenanceWindowById, - getMaintenanceWindowsByTeamId, - getMaintenanceWindowsByMonitorId, - deleteMaintenanceWindow, - editMaintenanceWindow, + createMaintenanceWindows, + getMaintenanceWindowById, + getMaintenanceWindowsByTeamId, + getMaintenanceWindowsByMonitorId, + deleteMaintenanceWindow, + editMaintenanceWindow, } from "../controllers/maintenanceWindowController.js"; import { verifyOwnership } from "../middleware/verifyOwnership.js"; import Monitor from "../db/models/Monitor.js"; @@ -15,9 +15,9 @@ const router = Router(); router.post("/", createMaintenanceWindows); router.get( - "/monitor/:monitorId", - verifyOwnership(Monitor, "monitorId"), - getMaintenanceWindowsByMonitorId + "/monitor/:monitorId", + verifyOwnership(Monitor, "monitorId"), + getMaintenanceWindowsByMonitorId ); router.get("/team/", getMaintenanceWindowsByTeamId); diff --git a/Server/routes/monitorRoute.js b/Server/routes/monitorRoute.js index d6ba03ece..f306a5146 100644 --- a/Server/routes/monitorRoute.js +++ b/Server/routes/monitorRoute.js @@ -1,17 +1,18 @@ import { Router } from "express"; import { - getAllMonitors, - getMonitorStatsById, - getMonitorCertificate, - getMonitorById, - getMonitorsAndSummaryByTeamId, - getMonitorsByTeamId, - createMonitor, - deleteMonitor, - deleteAllMonitors, - editMonitor, - pauseMonitor, - addDemoMonitors, + getAllMonitors, + getMonitorStatsById, + getMonitorCertificate, + getMonitorById, + getMonitorsAndSummaryByTeamId, + getMonitorsByTeamId, + createMonitor, + checkEndpointResolution, + deleteMonitor, + deleteAllMonitors, + editMonitor, + pauseMonitor, + addDemoMonitors, } from "../controllers/monitorController.js"; import { isAllowed } from "../middleware/isAllowed.js"; import { fetchMonitorCertificate } from "../controllers/controllerUtils.js"; @@ -21,15 +22,25 @@ const router = Router(); router.get("/", getAllMonitors); router.get("/stats/:monitorId", getMonitorStatsById); router.get("/certificate/:monitorId", (req, res, next) => { - getMonitorCertificate(req, res, next, fetchMonitorCertificate); + getMonitorCertificate(req, res, next, fetchMonitorCertificate); }); router.get("/:monitorId", getMonitorById); router.get("/team/summary/:teamId", getMonitorsAndSummaryByTeamId); router.get("/team/:teamId", getMonitorsByTeamId); -router.post("/", isAllowed(["admin", "superadmin"]), createMonitor); +router.get( + "/resolution/url", + isAllowed(["admin", "superadmin"]), + checkEndpointResolution +) -router.delete("/:monitorId", isAllowed(["admin", "superadmin"]), deleteMonitor); +router.delete( + "/:monitorId", + isAllowed(["admin", "superadmin"]), + deleteMonitor +); + +router.post("/", isAllowed(["admin", "superadmin"]), createMonitor); router.put("/:monitorId", isAllowed(["admin", "superadmin"]), editMonitor); diff --git a/Server/routes/queueRoute.js b/Server/routes/queueRoute.js index 7d0a262ff..addd9dab8 100644 --- a/Server/routes/queueRoute.js +++ b/Server/routes/queueRoute.js @@ -1,9 +1,9 @@ import { Router } from "express"; import { - getMetrics, - getJobs, - addJob, - obliterateQueue, + getMetrics, + getJobs, + addJob, + obliterateQueue, } from "../controllers/queueController.js"; const router = Router(); diff --git a/Server/routes/settingsRoute.js b/Server/routes/settingsRoute.js index 6970f926b..1b8dc19ad 100644 --- a/Server/routes/settingsRoute.js +++ b/Server/routes/settingsRoute.js @@ -1,8 +1,5 @@ import { Router } from "express"; -import { - getAppSettings, - updateAppSettings, -} from "../controllers/settingsController.js"; +import { getAppSettings, updateAppSettings } from "../controllers/settingsController.js"; import { isAllowed } from "../middleware/isAllowed.js"; const router = Router(); diff --git a/Server/service/emailService.js b/Server/service/emailService.js index 7bcaa31f0..519162ba0 100644 --- a/Server/service/emailService.js +++ b/Server/service/emailService.js @@ -1,11 +1,6 @@ -import fs from "fs"; -import path from "path"; -import nodemailer from "nodemailer"; -import pkg from "handlebars"; -const { compile } = pkg; -import mjml2html from "mjml"; -import logger from "../utils/logger.js"; import { fileURLToPath } from "url"; +import path from "path"; + const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); @@ -15,128 +10,123 @@ const SERVICE_NAME = "EmailService"; * Represents an email service that can load templates, build, and send emails. */ class EmailService { - /** - * Constructs an instance of the EmailService, initializing template loaders and the email transporter. - */ - constructor(settingsService) { - this.settingsService = settingsService; - /** - * Loads an email template from the filesystem. - * - * @param {string} templateName - The name of the template to load. - * @returns {Function} A compiled template function that can be used to generate HTML email content. - */ - this.loadTemplate = (templateName) => { - try { - const templatePath = path.join( - __dirname, - `../templates/${templateName}.mjml` - ); - const templateContent = fs.readFileSync(templatePath, "utf8"); - return compile(templateContent); - } catch (error) { - logger.error("Error loading Email templates", { - error, - service: this.SERVICE_NAME, - }); - } - }; + /** + * Constructs an instance of the EmailService, initializing template loaders and the email transporter. + * @param {Object} settingsService - The settings service to get email configuration. + * @param {Object} fs - The file system module. + * @param {Object} path - The path module. + * @param {Function} compile - The Handlebars compile function. + * @param {Function} mjml2html - The MJML to HTML conversion function. + * @param {Object} nodemailer - The nodemailer module. + * @param {Object} logger - The logger module. + */ + constructor(settingsService, fs, path, compile, mjml2html, nodemailer, logger) { + this.settingsService = settingsService; + this.fs = fs; + this.path = path; + this.compile = compile; + this.mjml2html = mjml2html; + this.nodemailer = nodemailer; + this.logger = logger; - /** - * A lookup object to access preloaded email templates. - * @type {Object.} - * TODO Load less used templates in their respective functions - */ - this.templateLookup = { - welcomeEmailTemplate: this.loadTemplate("welcomeEmail"), - employeeActivationTemplate: this.loadTemplate("employeeActivation"), - noIncidentsThisWeekTemplate: this.loadTemplate("noIncidentsThisWeek"), - serverIsDownTemplate: this.loadTemplate("serverIsDown"), - serverIsUpTemplate: this.loadTemplate("serverIsUp"), - passwordResetTemplate: this.loadTemplate("passwordReset"), - }; + /** + * Loads an email template from the filesystem. + * + * @param {string} templateName - The name of the template to load. + * @returns {Function} A compiled template function that can be used to generate HTML email content. + */ + this.loadTemplate = (templateName) => { + try { + const templatePath = this.path.join( + __dirname, + `../templates/${templateName}.mjml` + ); + const templateContent = this.fs.readFileSync(templatePath, "utf8"); + return this.compile(templateContent); + } catch (error) { + this.logger.error("Error loading Email templates", { + error, + service: this.SERVICE_NAME, + }); + } + }; - /** - * The email transporter used to send emails. - * @type {Object} - */ + /** + * A lookup object to access preloaded email templates. + * @type {Object.} + * TODO Load less used templates in their respective functions + */ + this.templateLookup = { + welcomeEmailTemplate: this.loadTemplate("welcomeEmail"), + employeeActivationTemplate: this.loadTemplate("employeeActivation"), + noIncidentsThisWeekTemplate: this.loadTemplate("noIncidentsThisWeek"), + serverIsDownTemplate: this.loadTemplate("serverIsDown"), + serverIsUpTemplate: this.loadTemplate("serverIsUp"), + passwordResetTemplate: this.loadTemplate("passwordReset"), + }; - const { - systemEmailHost, - systemEmailPort, - systemEmailAddress, - systemEmailPassword, - } = this.settingsService.getSettings(); + /** + * The email transporter used to send emails. + * @type {Object} + */ - const emailConfig = { - host: systemEmailHost, - port: systemEmailPort, - secure: true, - auth: { - user: systemEmailAddress, - pass: systemEmailPassword, - }, - }; + const { systemEmailHost, systemEmailPort, systemEmailAddress, systemEmailPassword } = + this.settingsService.getSettings(); - this.transporter = nodemailer.createTransport(emailConfig); - } + const emailConfig = { + host: systemEmailHost, + port: systemEmailPort, + secure: true, + auth: { + user: systemEmailAddress, + pass: systemEmailPassword, + }, + }; - /** - * Asynchronously builds and sends an email using a specified template and context. - * - * @param {string} template - The name of the template to use for the email body. - * @param {Object} context - The data context to render the template with. - * @param {string} to - The recipient's email address. - * @param {string} subject - The subject of the email. - * @returns {Promise} A promise that resolves to the messageId of the sent email. - */ - buildAndSendEmail = async (template, context, to, subject) => { - const buildHtml = async (template, context) => { - try { - const mjml = this.templateLookup[template](context); - const html = await mjml2html(mjml); - return html.html; - } catch (error) { - logger.error("Error building Email HTML", { - error, - service: SERVICE_NAME, - }); - } - }; + this.transporter = this.nodemailer.createTransport(emailConfig); + } - const sendEmail = async (to, subject, html) => { - try { - const info = await this.transporter.sendMail({ - to: to, - subject: subject, - html: html, - }); - return info; - } catch (error) { - logger.error("Error sending Email", { - error, - service: SERVICE_NAME, - }); - } - }; + /** + * Asynchronously builds and sends an email using a specified template and context. + * + * @param {string} template - The name of the template to use for the email body. + * @param {Object} context - The data context to render the template with. + * @param {string} to - The recipient's email address. + * @param {string} subject - The subject of the email. + * @returns {Promise} A promise that resolves to the messageId of the sent email. + */ + buildAndSendEmail = async (template, context, to, subject) => { + const buildHtml = async (template, context) => { + try { + const mjml = this.templateLookup[template](context); + const html = await this.mjml2html(mjml); + return html.html; + } catch (error) { + this.logger.error("Error building Email HTML", { + error, + service: SERVICE_NAME, + }); + } + }; - try { - const info = await sendEmail( - to, - subject, - await buildHtml(template, context) - ); - return info.messageId; - } catch (error) { - error.service = SERVICE_NAME; - if (error.method === undefined) { - error.method = "buildAndSendEmail"; - } - logger.error("Error building and sending Email", { - error, - service: SERVICE_NAME, - }); - } - }; + const sendEmail = async (to, subject, html) => { + try { + const info = await this.transporter.sendMail({ + to: to, + subject: subject, + html: html, + }); + return info; + } catch (error) { + this.logger.error("Error sending Email", { + error, + service: SERVICE_NAME, + }); + } + }; + const html = await buildHtml(template, context); + const info = await sendEmail(to, subject, html); + return info?.messageId; + }; } export default EmailService; diff --git a/Server/service/jobQueue.js b/Server/service/jobQueue.js index b8e56a7d3..f8493911e 100644 --- a/Server/service/jobQueue.js +++ b/Server/service/jobQueue.js @@ -13,145 +13,142 @@ const SERVICE_NAME = "JobQueue"; * It scales the number of workers based on the number of jobs/worker */ class JobQueue { - /** - * Constructs a new JobQueue - * @constructor - * @param {SettingsService} settingsService - The settings service - * @throws {Error} - */ - constructor(settingsService) { - const { redisHost, redisPort } = settingsService.getSettings(); - const connection = { - host: redisHost || "127.0.0.1", - port: redisPort || 6379, - }; - this.connection = connection; - this.queue = new Queue(QUEUE_NAME, { - connection, - }); - this.workers = []; - this.db = null; - this.networkService = null; - this.settingsService = settingsService; - } + /** + * Constructs a new JobQueue + * @constructor + * @param {SettingsService} settingsService - The settings service + * @throws {Error} + */ + constructor(settingsService) { + const { redisHost, redisPort } = settingsService.getSettings(); + const connection = { + host: redisHost || "127.0.0.1", + port: redisPort || 6379, + }; + this.connection = connection; + this.queue = new Queue(QUEUE_NAME, { + connection, + }); + this.workers = []; + this.db = null; + this.networkService = null; + this.settingsService = settingsService; + } - /** - * Static factory method to create a JobQueue - * @static - * @async - * @returns {Promise} - Returns a new JobQueue - * - */ - static async createJobQueue(db, networkService, settingsService) { - const queue = new JobQueue(settingsService); - try { - queue.db = db; - queue.networkService = networkService; - const monitors = await db.getAllMonitors(); - for (const monitor of monitors) { - if (monitor.isActive) { - await queue.addJob(monitor.id, monitor); - } - } - const workerStats = await queue.getWorkerStats(); - await queue.scaleWorkers(workerStats); - return queue; - } catch (error) { - error.service === undefined ? (error.service = SERVICE_NAME) : null; - error.method === undefined ? (error.method = "createJobQueue") : null; - throw error; - } - } + /** + * Static factory method to create a JobQueue + * @static + * @async + * @returns {Promise} - Returns a new JobQueue + * + */ + static async createJobQueue(db, networkService, settingsService) { + const queue = new JobQueue(settingsService); + try { + queue.db = db; + queue.networkService = networkService; + const monitors = await db.getAllMonitors(); + for (const monitor of monitors) { + if (monitor.isActive) { + await queue.addJob(monitor.id, monitor); + } + } + const workerStats = await queue.getWorkerStats(); + await queue.scaleWorkers(workerStats); + return queue; + } catch (error) { + error.service === undefined ? (error.service = SERVICE_NAME) : null; + error.method === undefined ? (error.method = "createJobQueue") : null; + throw error; + } + } - /** - * Creates a worker for the queue - * Operations are carried out in the async callback - * @returns {Worker} The newly created worker - */ - createWorker() { - const worker = new Worker( - QUEUE_NAME, - async (job) => { - try { - // Get all maintenance windows for this monitor - const monitorId = job.data._id; - const maintenanceWindows = - await this.db.getMaintenanceWindowsByMonitorId(monitorId); - // Check for active maintenance window: - const maintenanceWindowActive = maintenanceWindows.reduce( - (acc, window) => { - if (window.active) { - const start = new Date(window.start); - const end = new Date(window.end); - const now = new Date(); - const repeatInterval = window.repeat || 0; + /** + * Creates a worker for the queue + * Operations are carried out in the async callback + * @returns {Worker} The newly created worker + */ + createWorker() { + const worker = new Worker( + QUEUE_NAME, + async (job) => { + try { + // Get all maintenance windows for this monitor + const monitorId = job.data._id; + const maintenanceWindows = + await this.db.getMaintenanceWindowsByMonitorId(monitorId); + // Check for active maintenance window: + const maintenanceWindowActive = maintenanceWindows.reduce((acc, window) => { + if (window.active) { + const start = new Date(window.start); + const end = new Date(window.end); + const now = new Date(); + const repeatInterval = window.repeat || 0; - while ((start < now) & (repeatInterval !== 0)) { - start.setTime(start.getTime() + repeatInterval); - end.setTime(end.getTime() + repeatInterval); - } + while ((start < now) & (repeatInterval !== 0)) { + start.setTime(start.getTime() + repeatInterval); + end.setTime(end.getTime() + repeatInterval); + } - if (start < now && end > now) { - return true; - } - } - return acc; - }, - false - ); + if (start < now && end > now) { + return true; + } + } + return acc; + }, false); - if (!maintenanceWindowActive) { - const res = await this.networkService.getStatus(job); - } else { - logger.info(`Monitor ${monitorId} is in maintenance window`, { - service: SERVICE_NAME, - monitorId, - }); - } - } catch (error) { - logger.error(`Error processing job ${job.id}: ${error.message}`, { - service: SERVICE_NAME, - jobId: job.id, - error: error, - }); - } - }, - { - connection: this.connection, - } - ); - return worker; - } + if (!maintenanceWindowActive) { + await this.networkService.getStatus(job); + } else { + logger.info(`Monitor ${monitorId} is in maintenance window`, { + service: SERVICE_NAME, + monitorId, + }); + } + } catch (error) { + logger.error(`Error processing job ${job.id}: ${error.message}`, { + service: SERVICE_NAME, + jobId: job.id, + error: error, + }); + } + }, + { + connection: this.connection, + } + ); + return worker; + } - /** - * @typedef {Object} WorkerStats - * @property {Array} jobs - Array of jobs in the Queue - * @property {number} - workerLoad - The number of jobs per worker - * - */ + /** + * @typedef {Object} WorkerStats + * @property {Array} jobs - Array of jobs in the Queue + * @property {number} - workerLoad - The number of jobs per worker + * + */ - /** - * Gets stats related to the workers - * This is used for scaling workers right now - * In the future we will likely want to scale based on server performance metrics - * CPU Usage & memory usage, if too high, scale down workers. - * When to scale up? If jobs are taking too long to complete? - * @async - * @returns {Promise} - Returns the worker stats - */ - async getWorkerStats() { - try { - const jobs = await this.queue.getRepeatableJobs(); - const load = jobs.length / this.workers.length; - return { jobs, load }; - } catch (error) { - error.service === undefined ? (error.service = SERVICE_NAME) : null; - error.method === undefined ? (error.method = "getWorkerStats") : null; - throw error; - } - } + /** + * Gets stats related to the workers + * This is used for scaling workers right now + * In the future we will likely want to scale based on server performance metrics + * CPU Usage & memory usage, if too high, scale down workers. + * When to scale up? If jobs are taking too long to complete? + * @async + * @returns {Promise} - Returns the worker stats + */ + async getWorkerStats() { + try { + const jobs = await this.queue.getRepeatableJobs(); + const load = jobs.length / this.workers.length; + return { jobs, load }; + } catch (error) { + error.service === undefined ? (error.service = SERVICE_NAME) : null; + error.method === undefined ? (error.method = "getWorkerStats") : null; + throw error; + } + } - /** + /** * Scale Workers * This function scales workers based on the load per worker * If the load is higher than the JOBS_PER_WORKER threshold, we add more workers @@ -163,201 +160,200 @@ class JobQueue { * @param {WorkerStats} workerStats - The payload for the job. * @returns {Promise} */ - async scaleWorkers(workerStats) { - if (this.workers.length === 0) { - // There are no workers, need to add one - for (let i = 0; i < 5; i++) { - const worker = this.createWorker(); - this.workers.push(worker); - } - return true; - } + async scaleWorkers(workerStats) { + if (this.workers.length === 0) { + // There are no workers, need to add one + for (let i = 0; i < 5; i++) { + const worker = this.createWorker(); + this.workers.push(worker); + } + return true; + } - if (workerStats.load > JOBS_PER_WORKER) { - // Find out how many more jobs we have than current workers can handle - const excessJobs = - workerStats.jobs.length - this.workers.length * JOBS_PER_WORKER; + if (workerStats.load > JOBS_PER_WORKER) { + // Find out how many more jobs we have than current workers can handle + const excessJobs = workerStats.jobs.length - this.workers.length * JOBS_PER_WORKER; - // Divide by jobs/worker to find out how many workers to add - const workersToAdd = Math.ceil(excessJobs / JOBS_PER_WORKER); - for (let i = 0; i < workersToAdd; i++) { - const worker = this.createWorker(); - this.workers.push(worker); - } - return true; - } + // Divide by jobs/worker to find out how many workers to add + const workersToAdd = Math.ceil(excessJobs / JOBS_PER_WORKER); + for (let i = 0; i < workersToAdd; i++) { + const worker = this.createWorker(); + this.workers.push(worker); + } + return true; + } - if (workerStats.load < JOBS_PER_WORKER) { - // Find out how much excess capacity we have - const workerCapacity = this.workers.length * JOBS_PER_WORKER; - const excessCapacity = workerCapacity - workerStats.jobs.length; - // Calculate how many workers to remove - const workersToRemove = Math.floor(excessCapacity / JOBS_PER_WORKER); - if (this.workers.length > 5) { - for (let i = 0; i < workersToRemove; i++) { - const worker = this.workers.pop(); - try { - await worker.close(); - } catch (error) { - // Catch the error instead of throwing it - logger.error(errorMessages.JOB_QUEUE_WORKER_CLOSE, { - service: SERVICE_NAME, - }); - } - } - } - return true; - } - return false; - } + if (workerStats.load < JOBS_PER_WORKER) { + // Find out how much excess capacity we have + const workerCapacity = this.workers.length * JOBS_PER_WORKER; + const excessCapacity = workerCapacity - workerStats.jobs.length; + // Calculate how many workers to remove + const workersToRemove = Math.floor(excessCapacity / JOBS_PER_WORKER); + if (this.workers.length > 5) { + for (let i = 0; i < workersToRemove; i++) { + const worker = this.workers.pop(); + try { + await worker.close(); + } catch (error) { + // Catch the error instead of throwing it + logger.error(errorMessages.JOB_QUEUE_WORKER_CLOSE, { + service: SERVICE_NAME, + }); + } + } + } + return true; + } + return false; + } - /** - * Gets all jobs in the queue. - * - * @async - * @returns {Promise>} - * @throws {Error} - Throws error if getting jobs fails - */ - async getJobs() { - try { - const jobs = await this.queue.getRepeatableJobs(); - return jobs; - } catch (error) { - error.service === undefined ? (error.service = SERVICE_NAME) : null; - error.method === undefined ? (error.method = "getJobs") : null; - throw error; - } - } + /** + * Gets all jobs in the queue. + * + * @async + * @returns {Promise>} + * @throws {Error} - Throws error if getting jobs fails + */ + async getJobs() { + try { + const jobs = await this.queue.getRepeatableJobs(); + return jobs; + } catch (error) { + error.service === undefined ? (error.service = SERVICE_NAME) : null; + error.method === undefined ? (error.method = "getJobs") : null; + throw error; + } + } - async getJobStats() { - try { - const jobs = await this.queue.getJobs(); - const ret = await Promise.all( - jobs.map(async (job) => { - const state = await job.getState(); - return { url: job.data.url, state }; - }) - ); - return { jobs: ret, workers: this.workers.length }; - } catch (error) { - error.service === undefined ? (error.service = SERVICE_NAME) : null; - error.method === undefined ? (error.method = "getJobStats") : null; - throw error; - } - } + async getJobStats() { + try { + const jobs = await this.queue.getJobs(); + const ret = await Promise.all( + jobs.map(async (job) => { + const state = await job.getState(); + return { url: job.data.url, state }; + }) + ); + return { jobs: ret, workers: this.workers.length }; + } catch (error) { + error.service === undefined ? (error.service = SERVICE_NAME) : null; + error.method === undefined ? (error.method = "getJobStats") : null; + throw error; + } + } - /** - * Adds a job to the queue and scales workers based on worker stats. - * - * @async - * @param {string} jobName - The name of the job to be added. - * @param {Monitor} payload - The payload for the job. - * @throws {Error} - Will throw an error if the job cannot be added or workers don't scale - */ - async addJob(jobName, payload) { - try { - console.log("Adding job", payload?.url ?? "No URL"); - // Execute job immediately - await this.queue.add(jobName, payload); - await this.queue.add(jobName, payload, { - repeat: { - every: payload?.interval ?? 60000, - }, - }); - const workerStats = await this.getWorkerStats(); - await this.scaleWorkers(workerStats); - } catch (error) { - error.service === undefined ? (error.service = SERVICE_NAME) : null; - error.method === undefined ? (error.method = "addJob") : null; - throw error; - } - } + /** + * Adds a job to the queue and scales workers based on worker stats. + * + * @async + * @param {string} jobName - The name of the job to be added. + * @param {Monitor} payload - The payload for the job. + * @throws {Error} - Will throw an error if the job cannot be added or workers don't scale + */ + async addJob(jobName, payload) { + try { + console.log("Adding job", payload?.url ?? "No URL"); + // Execute job immediately + await this.queue.add(jobName, payload); + await this.queue.add(jobName, payload, { + repeat: { + every: payload?.interval ?? 60000, + }, + }); + const workerStats = await this.getWorkerStats(); + await this.scaleWorkers(workerStats); + } catch (error) { + error.service === undefined ? (error.service = SERVICE_NAME) : null; + error.method === undefined ? (error.method = "addJob") : null; + throw error; + } + } - /** - * Deletes a job from the queue. - * - * @async - * @param {Monitor} monitor - The monitor to remove. - * @throws {Error} - */ - async deleteJob(monitor) { - try { - const deleted = await this.queue.removeRepeatable(monitor._id, { - every: monitor.interval, - }); - if (deleted) { - logger.info(successMessages.JOB_QUEUE_DELETE_JOB, { - service: SERVICE_NAME, - jobId: monitor.id, - }); - const workerStats = await this.getWorkerStats(); - await this.scaleWorkers(workerStats); - } else { - logger.error(errorMessages.JOB_QUEUE_DELETE_JOB, { - service: SERVICE_NAME, - jobId: monitor.id, - }); - } - } catch (error) { - error.service === undefined ? (error.service = SERVICE_NAME) : null; - error.method === undefined ? (error.method = "deleteJob") : null; - throw error; - } - } + /** + * Deletes a job from the queue. + * + * @async + * @param {Monitor} monitor - The monitor to remove. + * @throws {Error} + */ + async deleteJob(monitor) { + try { + const deleted = await this.queue.removeRepeatable(monitor._id, { + every: monitor.interval, + }); + if (deleted) { + logger.info(successMessages.JOB_QUEUE_DELETE_JOB, { + service: SERVICE_NAME, + jobId: monitor.id, + }); + const workerStats = await this.getWorkerStats(); + await this.scaleWorkers(workerStats); + } else { + logger.error(errorMessages.JOB_QUEUE_DELETE_JOB, { + service: SERVICE_NAME, + jobId: monitor.id, + }); + } + } catch (error) { + error.service === undefined ? (error.service = SERVICE_NAME) : null; + error.method === undefined ? (error.method = "deleteJob") : null; + throw error; + } + } - async getMetrics() { - try { - const metrics = { - waiting: await this.queue.getWaitingCount(), - active: await this.queue.getActiveCount(), - completed: await this.queue.getCompletedCount(), - failed: await this.queue.getFailedCount(), - delayed: await this.queue.getDelayedCount(), - repeatableJobs: (await this.queue.getRepeatableJobs()).length, - }; - return metrics; - } catch (error) { - logger.error("Failed to retrieve job queue metrics", { - service: SERVICE_NAME, - errorMsg: error.message, - }); - } - } + async getMetrics() { + try { + const metrics = { + waiting: await this.queue.getWaitingCount(), + active: await this.queue.getActiveCount(), + completed: await this.queue.getCompletedCount(), + failed: await this.queue.getFailedCount(), + delayed: await this.queue.getDelayedCount(), + repeatableJobs: (await this.queue.getRepeatableJobs()).length, + }; + return metrics; + } catch (error) { + logger.error("Failed to retrieve job queue metrics", { + service: SERVICE_NAME, + errorMsg: error.message, + }); + } + } - /** - * @async - * @returns {Promise} - Returns true if obliteration is successful - */ - async obliterate() { - try { - let metrics = await this.getMetrics(); - console.log(metrics); - await this.queue.pause(); - const jobs = await this.getJobs(); + /** + * @async + * @returns {Promise} - Returns true if obliteration is successful + */ + async obliterate() { + try { + let metrics = await this.getMetrics(); + console.log(metrics); + await this.queue.pause(); + const jobs = await this.getJobs(); - for (const job of jobs) { - await this.queue.removeRepeatableByKey(job.key); - await this.queue.remove(job.id); - } - await Promise.all( - this.workers.map(async (worker) => { - await worker.close(); - }) - ); + for (const job of jobs) { + await this.queue.removeRepeatableByKey(job.key); + await this.queue.remove(job.id); + } + await Promise.all( + this.workers.map(async (worker) => { + await worker.close(); + }) + ); - await this.queue.obliterate(); - metrics = await this.getMetrics(); - console.log(metrics); - logger.info(successMessages.JOB_QUEUE_OBLITERATE, { - service: SERVICE_NAME, - }); - return true; - } catch (error) { - error.service === undefined ? (error.service = SERVICE_NAME) : null; - error.method === undefined ? (error.method = "obliterate") : null; - throw error; - } - } + await this.queue.obliterate(); + metrics = await this.getMetrics(); + console.log(metrics); + logger.info(successMessages.JOB_QUEUE_OBLITERATE, { + service: SERVICE_NAME, + }); + return true; + } catch (error) { + error.service === undefined ? (error.service = SERVICE_NAME) : null; + error.method === undefined ? (error.method = "obliterate") : null; + throw error; + } + } } export default JobQueue; diff --git a/Server/service/networkService.js b/Server/service/networkService.js index 158bff62b..e90180835 100644 --- a/Server/service/networkService.js +++ b/Server/service/networkService.js @@ -1,7 +1,3 @@ -import axios from "axios"; -import ping from "ping"; -import logger from "../utils/logger.js"; -import http from "http"; import { errorMessages, successMessages } from "../utils/messages.js"; /** @@ -12,321 +8,423 @@ import { errorMessages, successMessages } from "../utils/messages.js"; */ class NetworkService { - constructor(db, emailService) { - this.db = db; - this.emailService = emailService; - this.TYPE_PING = "ping"; - this.TYPE_HTTP = "http"; - this.TYPE_PAGESPEED = "pagespeed"; - this.SERVICE_NAME = "NetworkService"; - this.NETWORK_ERROR = 5000; - } + /** + * Creates an instance of NetworkService. + * + * @param {Object} db - The database service. + * @param {Object} emailService - The email service. + * @param {Object} axios - The axios HTTP client. + * @param {Object} ping - The ping service. + * @param {Object} logger - The logging service. + * @param {Object} http - The HTTP service. + */ + constructor(db, emailService, axios, ping, logger, http) { + this.db = db; + this.emailService = emailService; + this.TYPE_PING = "ping"; + this.TYPE_HTTP = "http"; + this.TYPE_PAGESPEED = "pagespeed"; + this.TYPE_HARDWARE = "hardware"; + this.SERVICE_NAME = "NetworkService"; + this.NETWORK_ERROR = 5000; + this.axios = axios; + this.ping = ping; + this.logger = logger; + this.http = http; + } - async handleNotification(monitor, isAlive) { - try { - let template = - isAlive === true ? "serverIsUpTemplate" : "serverIsDownTemplate"; - let status = isAlive === true ? "up" : "down"; + /** + * Handles the notification process for a monitor. + * + * @param {Object} monitor - The monitor object containing monitor details. + * @param {boolean} isAlive - The status of the monitor (true if up, false if down). + * @returns {Promise} + */ async handleNotification(monitor, isAlive) { + try { + let template = isAlive === true ? "serverIsUpTemplate" : "serverIsDownTemplate"; + let status = isAlive === true ? "up" : "down"; - const notifications = await this.db.getNotificationsByMonitorId( - monitor._id - ); - for (const notification of notifications) { - if (notification.type === "email") { - await this.emailService.buildAndSendEmail( - template, - { monitorName: monitor.name, monitorUrl: monitor.url }, - notification.address, - `Monitor ${monitor.name} is ${status}` - ); - } - } - } catch (error) { - logger.error(error.message, { - method: "handleNotification", - service: this.SERVICE_NAME, - monitorId: monitor._id, - }); - } - } + const notifications = await this.db.getNotificationsByMonitorId(monitor._id); + for (const notification of notifications) { + if (notification.type === "email") { + await this.emailService.buildAndSendEmail( + template, + { monitorName: monitor.name, monitorUrl: monitor.url }, + notification.address, + `Monitor ${monitor.name} is ${status}` + ); + } + } + } catch (error) { + this.logger.error(error.message, { + method: "handleNotification", + service: this.SERVICE_NAME, + monitorId: monitor._id, + }); + } + } - async handleStatusUpdate(job, isAlive) { - let monitor; - // Look up the monitor, if it doesn't exist, it's probably been removed, return - try { - const { _id } = job.data; - monitor = await this.db.getMonitorById(_id); - } catch (error) { - return; - } + /** + * Handles the status update for a monitor job. + * + * @param {Object} job - The job object containing job details. + * @param {boolean} isAlive - The status of the monitor (true if up, false if down). + * @returns {Promise} + */ + async handleStatusUpdate(job, isAlive) { + let monitor; + const { _id } = job.data; - // Otherwise, try to update monitor status - try { - if (monitor === null || monitor === undefined) { - logger.error(`Null Monitor: ${_id}`, { - method: "handleStatusUpdate", - service: this.SERVICE_NAME, - jobId: job.id, - }); - return; - } - if (monitor.status === undefined || monitor.status !== isAlive) { - const oldStatus = monitor.status; - monitor.status = isAlive; - await monitor.save(); + // Look up the monitor, if it doesn't exist, it's probably been removed, return + try { + monitor = await this.db.getMonitorById(_id); + } catch (error) { + return; + } - if (oldStatus !== undefined && oldStatus !== isAlive) { - this.handleNotification(monitor, isAlive); - } - } - } catch (error) { - logger.error(error.message, { - method: "handleStatusUpdate", - service: this.SERVICE_NAME, - jobId: job.id, - }); - } - } + // Otherwise, try to update monitor status + try { + if (monitor === null || monitor === undefined) { + this.logger.error(`Null Monitor: ${_id}`, { + method: "handleStatusUpdate", + service: this.SERVICE_NAME, + jobId: job.id, + }); + return; + } + if (monitor.status === undefined || monitor.status !== isAlive) { + const oldStatus = monitor.status; + monitor.status = isAlive; + await monitor.save(); - /** - * Measures the response time of an asynchronous operation. - * @param {Function} operation - An asynchronous operation to measure. - * @returns {Promise<{responseTime: number, response: any}>} An object containing the response time in milliseconds and the response from the operation. - * @throws {Error} The error object from the operation, contains response time. - */ - async measureResponseTime(operation) { - const startTime = Date.now(); - try { - const response = await operation(); - const endTime = Date.now(); - return { responseTime: endTime - startTime, response }; - } catch (error) { - const endTime = Date.now(); - error.responseTime = endTime - startTime; - error.service === undefined ? (error.service = SERVICE_NAME) : null; - error.method === undefined - ? (error.method = "measureResponseTime") - : null; - throw error; - } - } + if (oldStatus !== undefined && oldStatus !== isAlive) { + this.handleNotification(monitor, isAlive); + } + } + } catch (error) { + this.logger.error(error.message, { + method: "handleStatusUpdate", + service: this.SERVICE_NAME, + jobId: job.id, + }); + } + } - /** - * Handles the ping operation for a given job, measures its response time, and logs the result. - * @param {Object} job - The job object containing data for the ping operation. - * @returns {Promise<{boolean}} The result of logging and storing the check - */ - async handlePing(job) { - const operation = async () => { - const response = await ping.promise.probe(job.data.url); - return response; - }; + /** + * Measures the response time of an asynchronous operation. + * @param {Function} operation - An asynchronous operation to measure. + * @returns {Promise<{responseTime: number, response: any}>} An object containing the response time in milliseconds and the response from the operation. + * @throws {Error} The error object from the operation, contains response time. + */ + async measureResponseTime(operation) { + const startTime = Date.now(); + try { + const response = await operation(); + const endTime = Date.now(); + return { responseTime: endTime - startTime, response }; + } catch (error) { + const endTime = Date.now(); + error.responseTime = endTime - startTime; + error.service === undefined ? (error.service = this.SERVICE_NAME) : null; + error.method === undefined ? (error.method = "measureResponseTime") : null; + throw error; + } + } - let isAlive; + /** + * Handles the ping operation for a given job, measures its response time, and logs the result. + * @param {Object} job - The job object containing data for the ping operation. + * @returns {Promise<{boolean}} The result of logging and storing the check + */ + async handlePing(job) { + let isAlive; - try { - const { responseTime, response } = - await this.measureResponseTime(operation); - isAlive = response.alive; - const checkData = { - monitorId: job.data._id, - status: isAlive, - responseTime, - message: isAlive - ? successMessages.PING_SUCCESS - : errorMessages.PING_CANNOT_RESOLVE, - }; - return await this.logAndStoreCheck(checkData, this.db.createCheck); - } catch (error) { - isAlive = false; - const checkData = { - monitorId: job.data._id, - status: isAlive, - message: errorMessages.PING_CANNOT_RESOLVE, - responseTime: error.responseTime, - }; - return await this.logAndStoreCheck(checkData, this.db.createCheck); - } finally { - this.handleStatusUpdate(job, isAlive); - } - } + const operation = async () => { + const response = await this.ping.promise.probe(job.data.url); + return response; + }; - /** - * Handles the http operation for a given job, measures its response time, and logs the result. - * @param {Object} job - The job object containing data for the ping operation. - * @returns {Promise<{boolean}} The result of logging and storing the check - */ - async handleHttp(job) { - // Define operation for timing - const operation = async () => { - const response = await axios.get(job.data.url); - return response; - }; + try { + const { responseTime, response } = await this.measureResponseTime(operation); + isAlive = response.alive; + const checkData = { + monitorId: job.data._id, + status: isAlive, + responseTime, + message: isAlive + ? successMessages.PING_SUCCESS + : errorMessages.PING_CANNOT_RESOLVE, + }; + await this.logAndStoreCheck(checkData, this.db.createCheck); + } catch (error) { + isAlive = false; + const checkData = { + monitorId: job.data._id, + status: isAlive, + message: errorMessages.PING_CANNOT_RESOLVE, + responseTime: error.responseTime, + }; + await this.logAndStoreCheck(checkData, this.db.createCheck); + } finally { + this.handleStatusUpdate(job, isAlive); + } + } - let isAlive; + /** + * Handles the http operation for a given job, measures its response time, and logs the result. + * @param {Object} job - The job object containing data for the ping operation. + * @returns {Promise<{boolean}} The result of logging and storing the check + */ + async handleHttp(job) { + // Define operation for timing + const operation = async () => { + const response = await this.axios.get(job.data.url); + return response; + }; - // attempt connection - try { - const { responseTime, response } = - await this.measureResponseTime(operation); - // check if response is in the 200 range, if so, service is up - isAlive = response.status >= 200 && response.status < 300; + let isAlive; - //Create a check with relevant data - const checkData = { - monitorId: job.data._id, - status: isAlive, - responseTime, - statusCode: response.status, - message: http.STATUS_CODES[response.status], - }; - return await this.logAndStoreCheck(checkData, this.db.createCheck); - } catch (error) { - const statusCode = error.response?.status || this.NETWORK_ERROR; - let message = http.STATUS_CODES[statusCode] || "Network Error"; - isAlive = false; - const checkData = { - monitorId: job.data._id, - status: isAlive, - statusCode, - responseTime: error.responseTime, - message, - }; + // attempt connection + try { + const { responseTime, response } = await this.measureResponseTime(operation); + // check if response is in the 200 range, if so, service is up + isAlive = response.status >= 200 && response.status < 300; - return await this.logAndStoreCheck(checkData, this.db.createCheck); - } finally { - this.handleStatusUpdate(job, isAlive); - } - } + //Create a check with relevant data + const checkData = { + monitorId: job.data._id, + status: isAlive, + responseTime, + statusCode: response.status, + message: this.http.STATUS_CODES[response.status], + }; + await this.logAndStoreCheck(checkData, this.db.createCheck); + } catch (error) { + const statusCode = error.response?.status || this.NETWORK_ERROR; + let message = this.http.STATUS_CODES[statusCode] || "Network Error"; + isAlive = false; + const checkData = { + monitorId: job.data._id, + status: isAlive, + statusCode, + responseTime: error.responseTime, + message, + }; + await this.logAndStoreCheck(checkData, this.db.createCheck); + } finally { + this.handleStatusUpdate(job, isAlive); + } + } - /** - * Handles PageSpeed job types by fetching and processing PageSpeed insights. - * - * This method sends a request to the Google PageSpeed Insights API to get performance metrics - * for the specified URL, then logs and stores the check results. - * - * @param {Object} job - The job object containing data related to the PageSpeed check. - * @param {string} job.data.url - The URL to be analyzed by the PageSpeed Insights API. - * @param {string} job.data._id - The unique identifier for the monitor associated with the check. - * - * @returns {Promise} A promise that resolves when the check results have been logged and stored. - * - * @throws {Error} Throws an error if there is an issue with fetching or processing the PageSpeed insights. - */ - async handlePagespeed(job) { - let isAlive; - try { - const url = job.data.url; - const response = await axios.get( - `https://pagespeedonline.googleapis.com/pagespeedonline/v5/runPagespeed?url=${url}&category=seo&category=accessibility&category=best-practices&category=performance` - ); - const pageSpeedResults = response.data; - const categories = pageSpeedResults.lighthouseResult?.categories; - const audits = pageSpeedResults.lighthouseResult?.audits; - const { - "cumulative-layout-shift": cls, - "speed-index": si, - "first-contentful-paint": fcp, - "largest-contentful-paint": lcp, - "total-blocking-time": tbt, - } = audits; + /** + * Handles PageSpeed job types by fetching and processing PageSpeed insights. + * + * This method sends a request to the Google PageSpeed Insights API to get performance metrics + * for the specified URL, then logs and stores the check results. + * + * @param {Object} job - The job object containing data related to the PageSpeed check. + * @param {string} job.data.url - The URL to be analyzed by the PageSpeed Insights API. + * @param {string} job.data._id - The unique identifier for the monitor associated with the check. + * + * @returns {Promise} A promise that resolves when the check results have been logged and stored. + * + * @throws {Error} Throws an error if there is an issue with fetching or processing the PageSpeed insights. + */ + async handlePagespeed(job) { + let isAlive; + try { + const url = job.data.url; - // Weights - // First Contentful Paint 10% - // Speed Index 10% - // Largest Contentful Paint 25% - // Total Blocking Time 30% - // Cumulative Layout Shift 25% + const response = await this.axios.get( + `https://pagespeedonline.googleapis.com/pagespeedonline/v5/runPagespeed?url=${url}&category=seo&category=accessibility&category=best-practices&category=performance` + ); + const pageSpeedResults = response.data; + const categories = pageSpeedResults.lighthouseResult?.categories; + const audits = pageSpeedResults.lighthouseResult?.audits; + const { + "cumulative-layout-shift": cls, + "speed-index": si, + "first-contentful-paint": fcp, + "largest-contentful-paint": lcp, + "total-blocking-time": tbt, + } = audits; + // Weights + // First Contentful Paint 10% + // Speed Index 10% + // Largest Contentful Paint 25% + // Total Blocking Time 30% + // Cumulative Layout Shift 25% - isAlive = true; - const checkData = { - monitorId: job.data._id, - status: isAlive, - statusCode: response.status, - message: http.STATUS_CODES[response.status], - accessibility: (categories.accessibility?.score || 0) * 100, - bestPractices: (categories["best-practices"]?.score || 0) * 100, - seo: (categories.seo?.score || 0) * 100, - performance: (categories.performance?.score || 0) * 100, - audits: { - cls, - si, - fcp, - lcp, - tbt, - }, - }; + isAlive = true; + const checkData = { + monitorId: job.data._id, + status: isAlive, + statusCode: response.status, + message: this.http.STATUS_CODES[response.status], + accessibility: (categories.accessibility?.score || 0) * 100, + bestPractices: (categories["best-practices"]?.score || 0) * 100, + seo: (categories.seo?.score || 0) * 100, + performance: (categories.performance?.score || 0) * 100, + audits: { + cls, + si, + fcp, + lcp, + tbt, + }, + }; + this.logAndStoreCheck(checkData, this.db.createPageSpeedCheck); + } catch (error) { + isAlive = false; + const statusCode = error.response?.status || this.NETWORK_ERROR; + const message = this.http.STATUS_CODES[statusCode] || "Network Error"; + const checkData = { + monitorId: job.data._id, + status: isAlive, + statusCode, + message, + accessibility: 0, + bestPractices: 0, + seo: 0, + performance: 0, + }; + this.logAndStoreCheck(checkData, this.db.createPageSpeedCheck); + } finally { + this.handleStatusUpdate(job, isAlive); + } + } - this.logAndStoreCheck(checkData, this.db.createPageSpeedCheck); - } catch (error) { - isAlive = false; - const statusCode = error.response?.status || this.NETWORK_ERROR; - const message = http.STATUS_CODES[statusCode] || "Network Error"; - const checkData = { - monitorId: job.data._id, - status: isAlive, - statusCode, - message, - accessibility: 0, - bestPractices: 0, - seo: 0, - performance: 0, - }; - this.logAndStoreCheck(checkData, this.db.createPageSpeedCheck); - } finally { - this.handleStatusUpdate(job, isAlive); - } - } + async handleHardware(job) { + const url = job.data.url; + let isAlive; + //TODO Fetch hardware data + //For now, fake hardware data: - /** - * Retrieves the status of a given job based on its type. - * For unsupported job types, it logs an error and returns false. - * - * @param {Object} job - The job object containing data necessary for processing. - * @returns {Promise} The status of the job if it is supported and processed successfully, otherwise false. - */ - async getStatus(job) { - switch (job.data.type) { - case this.TYPE_PING: - return await this.handlePing(job); - case this.TYPE_HTTP: - return await this.handleHttp(job); - case this.TYPE_PAGESPEED: - return await this.handlePagespeed(job); - default: - logger.error(`Unsupported type: ${job.data.type}`, { - service: this.SERVICE_NAME, - method: "getStatus", - jobId: job.id, - }); - return false; - } - } + const hardwareData = { + monitorId: job.data._id, + cpu: { + physical_core: 1, + logical_core: 1, + frequency: 266, + temperature: null, + free_percent: null, + usage_percent: null, + }, + memory: { + total_bytes: 4, + available_bytes: 4, + used_bytes: 2, + usage_percent: 0.5, + }, + disk: [ + { + read_speed_bytes: 3, + write_speed_bytes: 3, + total_bytes: 10, + free_bytes: 2, + usage_percent: 0.8, + }, + ], + host: { + os: "Linux", + platform: "Ubuntu", + kernel_version: "24.04", + }, + }; + try { + isAlive = true; + this.logAndStoreCheck(hardwareData, this.db.createHardwareCheck); + } catch (error) { + isAlive = false; + const nullData = { + monitorId: job.data._id, + cpu: { + physical_core: 0, + logical_core: 0, + frequency: 0, + temperature: 0, + free_percent: 0, + usage_percent: 0, + }, + memory: { + total_bytes: 0, + available_bytes: 0, + used_bytes: 0, + usage_percent: 0, + }, + disk: [ + { + read_speed_bytes: 0, + write_speed_bytes: 0, + total_bytes: 0, + free_bytes: 0, + usage_percent: 0, + }, + ], + host: { + os: "", + platform: "", + kernel_version: "", + }, + }; + this.logAndStoreCheck(nullData, this.db.createHardwareCheck); + } finally { + this.handleStatusUpdate(job, isAlive); + } + } - /** - * Logs and stores the result of a check for a specific job. - * - * @param {Object} data - Data to be written - * @param {function} writeToDB - DB write method - * - * @returns {Promise} The status of the inserted check if successful, otherwise false. - */ + /** + * Retrieves the status of a given job based on its type. + * For unsupported job types, it logs an error and returns false. + * + * @param {Object} job - The job object containing data necessary for processing. + * @returns {Promise} The status of the job if it is supported and processed successfully, otherwise false. + */ + async getStatus(job) { + switch (job.data.type) { + case this.TYPE_PING: + return await this.handlePing(job); + case this.TYPE_HTTP: + return await this.handleHttp(job); + case this.TYPE_PAGESPEED: + return await this.handlePagespeed(job); + case this.TYPE_HARDWARE: + return await this.handleHardware(job); + default: + this.logger.error(`Unsupported type: ${job.data.type}`, { + service: this.SERVICE_NAME, + method: "getStatus", + jobId: job.id, + }); + return false; + } + } - async logAndStoreCheck(data, writeToDB) { - try { - const insertedCheck = await writeToDB(data); - if (insertedCheck !== null && insertedCheck !== undefined) { - return insertedCheck.status; - } - } catch (error) { - logger.error(`Error wrtiting check for ${data.monitorId}`, { - service: this.SERVICE_NAME, - method: "logAndStoreCheck", - monitorId: data.monitorId, - error: error, - }); - } - } + /** + * Logs and stores the result of a check for a specific job. + * + * @param {Object} data - Data to be written + * @param {function} writeToDB - DB write method + * + * @returns {Promise} The status of the inserted check if successful, otherwise false. + */ + + async logAndStoreCheck(data, writeToDB) { + try { + const insertedCheck = await writeToDB(data); + if (insertedCheck !== null && insertedCheck !== undefined) { + return insertedCheck.status; + } + throw new Error(); + } catch (error) { + this.logger.error(`Error writing check for ${data.monitorId}`, { + service: this.SERVICE_NAME, + method: "logAndStoreCheck", + monitorId: data.monitorId, + error: error, + }); + } + } } export default NetworkService; diff --git a/Server/service/settingsService.js b/Server/service/settingsService.js index bac92cc12..9f44f7d07 100644 --- a/Server/service/settingsService.js +++ b/Server/service/settingsService.js @@ -1,22 +1,21 @@ -import AppSettings from "../db/models/AppSettings.js"; const SERVICE_NAME = "SettingsService"; const envConfig = { - logLevel: undefined, - apiBaseUrl: undefined, - clientHost: process.env.CLIENT_HOST, - jwtSecret: process.env.JWT_SECRET, - refreshTokenSecret: process.env.REFRESH_TOKEN_SECRET, - dbType: process.env.DB_TYPE, - dbConnectionString: process.env.DB_CONNECTION_STRING, - redisHost: process.env.REDIS_HOST, - redisPort: process.env.REDIS_PORT, - jwtTTL: process.env.TOKEN_TTL, - refreshTokenTTL: process.env.REFRESH_TOKEN_TTL, - pagespeedApiKey: process.env.PAGESPEED_API_KEY, - systemEmailHost: process.env.SYSTEM_EMAIL_HOST, - systemEmailPort: process.env.SYSTEM_EMAIL_PORT, - systemEmailAddress: process.env.SYSTEM_EMAIL_ADDRESS, - systemEmailPassword: process.env.SYSTEM_EMAIL_PASSWORD, + logLevel: undefined, + apiBaseUrl: undefined, + clientHost: process.env.CLIENT_HOST, + jwtSecret: process.env.JWT_SECRET, + refreshTokenSecret: process.env.REFRESH_TOKEN_SECRET, + dbType: process.env.DB_TYPE, + dbConnectionString: process.env.DB_CONNECTION_STRING, + redisHost: process.env.REDIS_HOST, + redisPort: process.env.REDIS_PORT, + jwtTTL: process.env.TOKEN_TTL, + refreshTokenTTL: process.env.REFRESH_TOKEN_TTL, + pagespeedApiKey: process.env.PAGESPEED_API_KEY, + systemEmailHost: process.env.SYSTEM_EMAIL_HOST, + systemEmailPort: process.env.SYSTEM_EMAIL_PORT, + systemEmailAddress: process.env.SYSTEM_EMAIL_ADDRESS, + systemEmailPassword: process.env.SYSTEM_EMAIL_PASSWORD, }; /** * SettingsService @@ -26,60 +25,57 @@ const envConfig = { * from the database if they are not set in the environment. */ class SettingsService { - /** - * Constructs a new SettingsService - * @constructor - * @throws {Error} - */ constructor() { - this.settings = { ...envConfig }; - } - /** - * Load settings from the database and merge with environment settings. - * If there are any settings that weren't set by environment variables, use user settings from the database. - * @returns {Promise} The merged settings. - * @throws Will throw an error if settings are not found in the database or if settings have not been loaded. - */ async loadSettings() { - try { - const dbSettings = await AppSettings.findOne(); - if (!this.settings) { - throw new Error("Settings not found"); - } + /** + * Constructs a new SettingsService + * @constructor + * @throws {Error} + */ constructor(appSettings) { + this.appSettings = appSettings; + this.settings = { ...envConfig }; + } + /** + * Load settings from the database and merge with environment settings. + * If there are any settings that weren't set by environment variables, use user settings from the database. + * @returns {Promise} The merged settings. + * @throws Will throw an error if settings are not found in the database or if settings have not been loaded. + */ async loadSettings() { + try { + const dbSettings = await this.appSettings.findOne(); + if (!this.settings) { + throw new Error("Settings not found"); + } - // If there are any settings that weren't set by environment variables, use user settings from DB - for (const key in envConfig) { - if (envConfig[key] === undefined && dbSettings[key] !== undefined) { - this.settings[key] = dbSettings[key]; - } - } - - if (!this.settings) { - throw new Error("Settings not found"); - } - return this.settings; - } catch (error) { - error.service === undefined ? (error.service = SERVICE_NAME) : null; - error.method === undefined ? (error.method = "loadSettings") : null; - throw error; - } - } - /** - * Reload settings by calling loadSettings. - * @returns {Promise} The reloaded settings. - */ - async reloadSettings() { - return this.loadSettings(); - } - /** - * Get the current settings. - * @returns {Object} The current settings. - * @throws Will throw an error if settings have not been loaded. - */ - getSettings() { - if (!this.settings) { - throw new Error("Settings have not been loaded"); - } - return this.settings; - } + // If there are any settings that weren't set by environment variables, use user settings from DB + for (const key in envConfig) { + if (envConfig[key] === undefined && dbSettings[key] !== undefined) { + this.settings[key] = dbSettings[key]; + } + } + return this.settings; + } catch (error) { + error.service === undefined ? (error.service = SERVICE_NAME) : null; + error.method === undefined ? (error.method = "loadSettings") : null; + throw error; + } + } + /** + * Reload settings by calling loadSettings. + * @returns {Promise} The reloaded settings. + */ + async reloadSettings() { + return this.loadSettings(); + } + /** + * Get the current settings. + * @returns {Object} The current settings. + * @throws Will throw an error if settings have not been loaded. + */ + getSettings() { + if (!this.settings) { + throw new Error("Settings have not been loaded"); + } + return this.settings; + } } export default SettingsService; diff --git a/Server/templates/employeeActivation.mjml b/Server/templates/employeeActivation.mjml index 9d3dc773a..ebc306418 100644 --- a/Server/templates/employeeActivation.mjml +++ b/Server/templates/employeeActivation.mjml @@ -1,35 +1,52 @@ - - - - - - - - - - - - Message from BlueWave Uptime Service - - - - - -

Hello {{name}}!

-

One of the admins created an account for you on the BlueWave Uptime server.

-

You can go ahead and create your account using this link.

-

{{link}}

-

Thank you.

-
-
- - - -

This email was sent by BlueWave Uptime.

-
-
-
-
-
\ No newline at end of file + + + + + + + + + + + + Message from BlueWave Uptime Service + + + + + +

Hello {{name}}!

+

+ One of the admins created an account for you on the BlueWave Uptime server. +

+

You can go ahead and create your account using this link.

+

{{link}}

+

Thank you.

+
+
+ + + +

This email was sent by BlueWave Uptime.

+
+
+
+
+ diff --git a/Server/templates/noIncidentsThisWeek.mjml b/Server/templates/noIncidentsThisWeek.mjml index 960a876fe..ca9ac0f23 100644 --- a/Server/templates/noIncidentsThisWeek.mjml +++ b/Server/templates/noIncidentsThisWeek.mjml @@ -1,38 +1,66 @@ - - - - - - - - - - - - Message from BlueWave Uptime Service - - - No incidents this week! - - - - - - -

Hello {{name}}!

-

There were no incidents this week. Good job!

-

Current monitors:

-

Google: 100% availability

-

Canada.ca:100% availability

-
-
- - - -

This email was sent by BlueWave Uptime.

-
-
-
-
-
\ No newline at end of file + + + + + + + + + + + + Message from BlueWave Uptime Service + + + No incidents this week! + + + + + + +

Hello {{name}}!

+

There were no incidents this week. Good job!

+

Current monitors:

+

Google: 100% availability

+

Canada.ca:100% availability

+
+
+ + + +

This email was sent by BlueWave Uptime.

+
+
+
+
+ diff --git a/Server/templates/passwordReset.mjml b/Server/templates/passwordReset.mjml index 480fcb572..4bc0ce80a 100644 --- a/Server/templates/passwordReset.mjml +++ b/Server/templates/passwordReset.mjml @@ -1,43 +1,56 @@ - - - - - - - - - - - - - Message from BlueWave Uptime Service - - - - - - -

Hello {{name}}!

-

- You are receiving this email because a password reset request - has been made for {{email}}. Please use the - link below on the site to reset your password. -

- Reset Password -

If you didn't request this, please ignore this email.

+ + + + + + + + + + + + + Message from BlueWave Uptime Service + + + + + + +

Hello {{name}}!

+

+ You are receiving this email because a password reset request has been made + for {{email}}. Please use the link below on the site to reset your password. +

+ Reset Password +

If you didn't request this, please ignore this email.

-

Thank you.

-
-
- - - -

This email was sent by BlueWave Uptime.

-
-
-
-
-
\ No newline at end of file +

Thank you.

+ + + + + +

This email was sent by BlueWave Uptime.

+
+
+ + + diff --git a/Server/templates/serverIsDown.mjml b/Server/templates/serverIsDown.mjml index 3caa6549c..ffe6ffc0e 100644 --- a/Server/templates/serverIsDown.mjml +++ b/Server/templates/serverIsDown.mjml @@ -1,57 +1,73 @@ - - - - - - - - - - - - - Message from BlueWave Uptime Service - - - - - Google.com is down - - - - - - - -

Hello {{name}}!

-

- We detected an incident on one of your monitors. Your service is - currently down. We'll send a message to you once it is up again. -

-

- Monitor name: {{monitor}} -

-

- URL: {{url}} -

-

- Problem: {{problem}} -

-

- Start date: {{startDate}} -

-
-
- - - - View incident details - - -

This email was sent by BlueWave Uptime.

-
-
-
-
-
\ No newline at end of file + + + + + + + + + + + + + Message from BlueWave Uptime Service + + + + + Google.com is down + + + + + + + +

Hello {{name}}!

+

+ We detected an incident on one of your monitors. Your service is currently + down. We'll send a message to you once it is up again. +

+

Monitor name: {{monitor}}

+

URL: {{url}}

+

Problem: {{problem}}

+

Start date: {{startDate}}

+
+
+ + + View incident details + +

This email was sent by BlueWave Uptime.

+
+
+
+
+ diff --git a/Server/templates/serverIsUp.mjml b/Server/templates/serverIsUp.mjml index 9e73a8559..154ad44d9 100644 --- a/Server/templates/serverIsUp.mjml +++ b/Server/templates/serverIsUp.mjml @@ -1,63 +1,72 @@ - - - - - - - - - - - - - Message from BlueWave Uptime Service - - - - - {{monitor}} is up - - - - - - - -

Hello {{name}}!

-

- Your latest incident is resolved and your monitored service is - up again. -

-

- Monitor name: {{monitor}} -

-

- URL: {{url}} -

-

- Problem: {{problem}} -

-

- Start date: {{startDate}} -

-

- Resolved date: {{resolvedDate}} -

-

- Duration:{{duration}} -

-
-
- - - - View incident details - - -

This email was sent by BlueWave Uptime.

-
-
-
-
-
\ No newline at end of file + + + + + + + + + + + + + Message from BlueWave Uptime Service + + + + + {{monitor}} is up + + + + + + + +

Hello {{name}}!

+

Your latest incident is resolved and your monitored service is up again.

+

Monitor name: {{monitor}}

+

URL: {{url}}

+

Problem: {{problem}}

+

Start date: {{startDate}}

+

Resolved date: {{resolvedDate}}

+

Duration:{{duration}}

+
+
+ + + View incident details + +

This email was sent by BlueWave Uptime.

+
+
+
+
+ diff --git a/Server/templates/welcomeEmail.mjml b/Server/templates/welcomeEmail.mjml index a58c9f017..27b816aa3 100644 --- a/Server/templates/welcomeEmail.mjml +++ b/Server/templates/welcomeEmail.mjml @@ -1,44 +1,57 @@ - - - - - - - - - - - - - Message from BlueWave Uptime Service - - - - - - -

Hello {{name}}!

-

- Thank you for trying out BlueWave Uptime! We developed it with - great care to meet our own needs, and we're excited to share it - with you. -

-

- BlueWave Uptime is an automated way of checking whether a - service such as a website or an application is available or not. -

-

We hope you find our service as valuable as we do.

-

Thank you.

-
-
- - - -

This email was sent by BlueWave Uptime.

-
-
-
-
-
\ No newline at end of file + + + + + + + + + + + + + Message from BlueWave Uptime Service + + + + + + +

Hello {{name}}!

+

+ Thank you for trying out BlueWave Uptime! We developed it with great care to + meet our own needs, and we're excited to share it with you. +

+

+ BlueWave Uptime is an automated way of checking whether a service such as a + website or an application is available or not. +

+

We hope you find our service as valuable as we do.

+

Thank you.

+
+
+ + + +

This email was sent by BlueWave Uptime.

+
+
+
+
+ diff --git a/Server/tests/controllers/checkController.test.js b/Server/tests/controllers/checkController.test.js index b6870086e..88e7d721e 100644 --- a/Server/tests/controllers/checkController.test.js +++ b/Server/tests/controllers/checkController.test.js @@ -1,375 +1,373 @@ import { - createCheck, - getChecks, - getTeamChecks, - deleteChecks, - deleteChecksByTeamId, - updateChecksTTL, + createCheck, + getChecks, + getTeamChecks, + deleteChecks, + deleteChecksByTeamId, + updateChecksTTL, } from "../../controllers/checkController.js"; import jwt from "jsonwebtoken"; import { errorMessages, successMessages } from "../../utils/messages.js"; import sinon from "sinon"; describe("Check Controller - createCheck", () => { - let req, res, next, handleError; - beforeEach(() => { - req = { - params: {}, - body: {}, - db: { - createCheck: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - handleError = sinon.stub(); - }); + let req, res, next, handleError; + beforeEach(() => { + req = { + params: {}, + body: {}, + db: { + createCheck: sinon.stub(), + }, + }; + res = { + status: sinon.stub().returnsThis(), + json: sinon.stub(), + }; + next = sinon.stub(); + handleError = sinon.stub(); + }); - afterEach(() => { - sinon.restore(); // Restore the original methods after each test - }); + afterEach(() => { + sinon.restore(); // Restore the original methods after each test + }); - it("should reject with a validation if params are invalid", async () => { - await createCheck(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); + it("should reject with a validation if params are invalid", async () => { + await createCheck(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].status).to.equal(422); + }); - it("should reject with a validation error if body is invalid", async () => { - req.params = { - monitorId: "monitorId", - }; - await createCheck(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); + it("should reject with a validation error if body is invalid", async () => { + req.params = { + monitorId: "monitorId", + }; + await createCheck(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].status).to.equal(422); + }); - it("should call next with error if data retrieval fails", async () => { - req.params = { - monitorId: "monitorId", - }; - req.body = { - monitorId: "monitorId", - status: true, - responseTime: 100, - statusCode: 200, - message: "message", - }; - req.db.createCheck.rejects(new Error("error")); - await createCheck(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - }); + it("should call next with error if data retrieval fails", async () => { + req.params = { + monitorId: "monitorId", + }; + req.body = { + monitorId: "monitorId", + status: true, + responseTime: 100, + statusCode: 200, + message: "message", + }; + req.db.createCheck.rejects(new Error("error")); + await createCheck(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + }); - it("should return a success message if check is created", async () => { - req.params = { - monitorId: "monitorId", - }; - req.db.createCheck.resolves({ id: "123" }); - req.body = { - monitorId: "monitorId", - status: true, - responseTime: 100, - statusCode: 200, - message: "message", - }; - await createCheck(req, res, next); - expect(res.status.calledWith(200)).to.be.true; - expect( - res.json.calledWith({ - success: true, - msg: successMessages.CHECK_CREATE, - data: { id: "123" }, - }) - ).to.be.true; - expect(next.notCalled).to.be.true; - }); + it("should return a success message if check is created", async () => { + req.params = { + monitorId: "monitorId", + }; + req.db.createCheck.resolves({ id: "123" }); + req.body = { + monitorId: "monitorId", + status: true, + responseTime: 100, + statusCode: 200, + message: "message", + }; + await createCheck(req, res, next); + expect(res.status.calledWith(200)).to.be.true; + expect( + res.json.calledWith({ + success: true, + msg: successMessages.CHECK_CREATE, + data: { id: "123" }, + }) + ).to.be.true; + expect(next.notCalled).to.be.true; + }); }); describe("Check Controller - getChecks", () => { - let req, res, next; - beforeEach(() => { - req = { - params: {}, - query: {}, - db: { - getChecks: sinon.stub(), - getChecksCount: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); + let req, res, next; + beforeEach(() => { + req = { + params: {}, + query: {}, + db: { + getChecks: sinon.stub(), + getChecksCount: sinon.stub(), + }, + }; + res = { + status: sinon.stub().returnsThis(), + json: sinon.stub(), + }; + next = sinon.stub(); + }); - afterEach(() => { - sinon.restore(); - }); + afterEach(() => { + sinon.restore(); + }); - it("should reject with a validation error if params are invalid", async () => { - await getChecks(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); + it("should reject with a validation error if params are invalid", async () => { + await getChecks(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].status).to.equal(422); + }); - it("should return a success message if checks are found", async () => { - req.params = { - monitorId: "monitorId", - }; - req.db.getChecks.resolves([{ id: "123" }]); - req.db.getChecksCount.resolves(1); - await getChecks(req, res, next); - expect(res.status.calledWith(200)).to.be.true; - expect( - res.json.calledWith({ - success: true, - msg: successMessages.CHECK_GET, - data: { checksCount: 1, checks: [{ id: "123" }] }, - }) - ).to.be.true; - expect(next.notCalled).to.be.true; - }); + it("should return a success message if checks are found", async () => { + req.params = { + monitorId: "monitorId", + }; + req.db.getChecks.resolves([{ id: "123" }]); + req.db.getChecksCount.resolves(1); + await getChecks(req, res, next); + expect(res.status.calledWith(200)).to.be.true; + expect( + res.json.calledWith({ + success: true, + msg: successMessages.CHECK_GET, + data: { checksCount: 1, checks: [{ id: "123" }] }, + }) + ).to.be.true; + expect(next.notCalled).to.be.true; + }); - it("should call next with error if data retrieval fails", async () => { - req.params = { - monitorId: "monitorId", - }; - req.db.getChecks.rejects(new Error("error")); - await getChecks(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - }); + it("should call next with error if data retrieval fails", async () => { + req.params = { + monitorId: "monitorId", + }; + req.db.getChecks.rejects(new Error("error")); + await getChecks(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + }); }); describe("Check Controller - getTeamChecks", () => { - let req, res, next; - beforeEach(() => { - req = { - params: {}, - query: {}, - db: { - getTeamChecks: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); + let req, res, next; + beforeEach(() => { + req = { + params: {}, + query: {}, + db: { + getTeamChecks: sinon.stub(), + }, + }; + res = { + status: sinon.stub().returnsThis(), + json: sinon.stub(), + }; + next = sinon.stub(); + }); - afterEach(() => { - sinon.restore(); - }); + afterEach(() => { + sinon.restore(); + }); - it("should reject with a validation error if params are invalid", async () => { - await getTeamChecks(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); + it("should reject with a validation error if params are invalid", async () => { + await getTeamChecks(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].status).to.equal(422); + }); - it("should return 200 and check data on successful validation and data retrieval", async () => { - req.params = { teamId: "1" }; - const checkData = [{ id: 1, name: "Check 1" }]; - req.db.getTeamChecks.resolves(checkData); + it("should return 200 and check data on successful validation and data retrieval", async () => { + req.params = { teamId: "1" }; + const checkData = [{ id: 1, name: "Check 1" }]; + req.db.getTeamChecks.resolves(checkData); - await getTeamChecks(req, res, next); - expect(req.db.getTeamChecks.calledOnceWith(req)).to.be.true; - expect(res.status.calledOnceWith(200)).to.be.true; - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.CHECK_GET, - data: checkData, - }) - ).to.be.true; - }); + await getTeamChecks(req, res, next); + expect(req.db.getTeamChecks.calledOnceWith(req)).to.be.true; + expect(res.status.calledOnceWith(200)).to.be.true; + expect( + res.json.calledOnceWith({ + success: true, + msg: successMessages.CHECK_GET, + data: checkData, + }) + ).to.be.true; + }); - it("should call next with error if data retrieval fails", async () => { - req.params = { teamId: "1" }; - req.db.getTeamChecks.rejects(new Error("Retrieval Error")); - await getTeamChecks(req, res, next); - expect(req.db.getTeamChecks.calledOnceWith(req)).to.be.true; - expect(next.firstCall.args[0]).to.be.an("error"); - expect(res.status.notCalled).to.be.true; - expect(res.json.notCalled).to.be.true; - }); + it("should call next with error if data retrieval fails", async () => { + req.params = { teamId: "1" }; + req.db.getTeamChecks.rejects(new Error("Retrieval Error")); + await getTeamChecks(req, res, next); + expect(req.db.getTeamChecks.calledOnceWith(req)).to.be.true; + expect(next.firstCall.args[0]).to.be.an("error"); + expect(res.status.notCalled).to.be.true; + expect(res.json.notCalled).to.be.true; + }); }); describe("Check Controller - deleteChecks", () => { - let req, res, next; - beforeEach(() => { - req = { - params: {}, - db: { - deleteChecks: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); + let req, res, next; + beforeEach(() => { + req = { + params: {}, + db: { + deleteChecks: sinon.stub(), + }, + }; + res = { + status: sinon.stub().returnsThis(), + json: sinon.stub(), + }; + next = sinon.stub(); + }); - afterEach(() => { - sinon.restore(); - }); + afterEach(() => { + sinon.restore(); + }); - it("should reject with an error if param validation fails", async () => { - await deleteChecks(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); + it("should reject with an error if param validation fails", async () => { + await deleteChecks(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].status).to.equal(422); + }); - it("should call next with error if data retrieval fails", async () => { - req.params = { monitorId: "1" }; - req.db.deleteChecks.rejects(new Error("Deletion Error")); - await deleteChecks(req, res, next); - expect(req.db.deleteChecks.calledOnceWith(req.params.monitorId)).to.be.true; - expect(next.firstCall.args[0]).to.be.an("error"); - expect(res.status.notCalled).to.be.true; - expect(res.json.notCalled).to.be.true; - }); + it("should call next with error if data retrieval fails", async () => { + req.params = { monitorId: "1" }; + req.db.deleteChecks.rejects(new Error("Deletion Error")); + await deleteChecks(req, res, next); + expect(req.db.deleteChecks.calledOnceWith(req.params.monitorId)).to.be.true; + expect(next.firstCall.args[0]).to.be.an("error"); + expect(res.status.notCalled).to.be.true; + expect(res.json.notCalled).to.be.true; + }); - it("should delete checks successfully", async () => { - req.params = { monitorId: "123" }; - req.db.deleteChecks.resolves(1); - await deleteChecks(req, res, next); - expect(req.db.deleteChecks.calledOnceWith(req.params.monitorId)).to.be.true; - expect(res.status.calledOnceWith(200)).to.be.true; - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.CHECK_DELETE, - data: { deletedCount: 1 }, - }) - ).to.be.true; - }); + it("should delete checks successfully", async () => { + req.params = { monitorId: "123" }; + req.db.deleteChecks.resolves(1); + await deleteChecks(req, res, next); + expect(req.db.deleteChecks.calledOnceWith(req.params.monitorId)).to.be.true; + expect(res.status.calledOnceWith(200)).to.be.true; + expect( + res.json.calledOnceWith({ + success: true, + msg: successMessages.CHECK_DELETE, + data: { deletedCount: 1 }, + }) + ).to.be.true; + }); }); describe("Check Controller - deleteChecksByTeamId", () => { - let req, res, next; - beforeEach(() => { - req = { - params: {}, - db: { - deleteChecksByTeamId: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); + let req, res, next; + beforeEach(() => { + req = { + params: {}, + db: { + deleteChecksByTeamId: sinon.stub(), + }, + }; + res = { + status: sinon.stub().returnsThis(), + json: sinon.stub(), + }; + next = sinon.stub(); + }); - afterEach(() => { - sinon.restore(); - }); + afterEach(() => { + sinon.restore(); + }); - it("should reject with an error if param validation fails", async () => { - await deleteChecksByTeamId(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); + it("should reject with an error if param validation fails", async () => { + await deleteChecksByTeamId(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].status).to.equal(422); + }); - it("should call next with error if data retrieval fails", async () => { - req.params = { teamId: "1" }; - req.db.deleteChecksByTeamId.rejects(new Error("Deletion Error")); - await deleteChecksByTeamId(req, res, next); - expect(req.db.deleteChecksByTeamId.calledOnceWith(req.params.teamId)).to.be - .true; - expect(next.firstCall.args[0]).to.be.an("error"); - expect(res.status.notCalled).to.be.true; - expect(res.json.notCalled).to.be.true; - }); + it("should call next with error if data retrieval fails", async () => { + req.params = { teamId: "1" }; + req.db.deleteChecksByTeamId.rejects(new Error("Deletion Error")); + await deleteChecksByTeamId(req, res, next); + expect(req.db.deleteChecksByTeamId.calledOnceWith(req.params.teamId)).to.be.true; + expect(next.firstCall.args[0]).to.be.an("error"); + expect(res.status.notCalled).to.be.true; + expect(res.json.notCalled).to.be.true; + }); - it("should delete checks successfully", async () => { - req.params = { teamId: "123" }; - req.db.deleteChecksByTeamId.resolves(1); - await deleteChecksByTeamId(req, res, next); - expect(req.db.deleteChecksByTeamId.calledOnceWith(req.params.teamId)).to.be - .true; - expect(res.status.calledOnceWith(200)).to.be.true; - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.CHECK_DELETE, - data: { deletedCount: 1 }, - }) - ).to.be.true; - }); + it("should delete checks successfully", async () => { + req.params = { teamId: "123" }; + req.db.deleteChecksByTeamId.resolves(1); + await deleteChecksByTeamId(req, res, next); + expect(req.db.deleteChecksByTeamId.calledOnceWith(req.params.teamId)).to.be.true; + expect(res.status.calledOnceWith(200)).to.be.true; + expect( + res.json.calledOnceWith({ + success: true, + msg: successMessages.CHECK_DELETE, + data: { deletedCount: 1 }, + }) + ).to.be.true; + }); }); describe("Check Controller - updateCheckTTL", () => { - let stub, req, res, next; - beforeEach(() => { - stub = sinon.stub(jwt, "verify").callsFake(() => { - return { teamId: "123" }; - }); + let stub, req, res, next; + beforeEach(() => { + stub = sinon.stub(jwt, "verify").callsFake(() => { + return { teamId: "123" }; + }); - req = { - body: {}, - headers: { authorization: "Bearer token" }, - settingsService: { - getSettings: sinon.stub().returns({ jwtSecret: "my_secret" }), - }, - db: { - updateChecksTTL: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); + req = { + body: {}, + headers: { authorization: "Bearer token" }, + settingsService: { + getSettings: sinon.stub().returns({ jwtSecret: "my_secret" }), + }, + db: { + updateChecksTTL: sinon.stub(), + }, + }; + res = { + status: sinon.stub().returnsThis(), + json: sinon.stub(), + }; + next = sinon.stub(); + }); - afterEach(() => { - sinon.restore(); - stub.restore(); - }); + afterEach(() => { + sinon.restore(); + stub.restore(); + }); - it("should reject if body validation fails", async () => { - await updateChecksTTL(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); + it("should reject if body validation fails", async () => { + await updateChecksTTL(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].status).to.equal(422); + }); - it("should throw a JwtError if verification fails", async () => { - stub.restore(); - req.body = { - ttl: 1, - }; - await updateChecksTTL(req, res, next); - expect(next.firstCall.args[0]).to.be.instanceOf(jwt.JsonWebTokenError); - }); + it("should throw a JwtError if verification fails", async () => { + stub.restore(); + req.body = { + ttl: 1, + }; + await updateChecksTTL(req, res, next); + expect(next.firstCall.args[0]).to.be.instanceOf(jwt.JsonWebTokenError); + }); - it("should call next with error if data retrieval fails", async () => { - req.body = { - ttl: 1, - }; - req.db.updateChecksTTL.rejects(new Error("Update Error")); - await updateChecksTTL(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - }); + it("should call next with error if data retrieval fails", async () => { + req.body = { + ttl: 1, + }; + req.db.updateChecksTTL.rejects(new Error("Update Error")); + await updateChecksTTL(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + }); - it("should update TTL successfully", async () => { - req.body = { - ttl: 1, - }; - req.db.updateChecksTTL.resolves(); - await updateChecksTTL(req, res, next); - expect(req.db.updateChecksTTL.calledOnceWith("123", 1 * 86400)).to.be.true; - expect(res.status.calledOnceWith(200)).to.be.true; - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.CHECK_UPDATE_TTL, - }) - ).to.be.true; - }); + it("should update TTL successfully", async () => { + req.body = { + ttl: 1, + }; + req.db.updateChecksTTL.resolves(); + await updateChecksTTL(req, res, next); + expect(req.db.updateChecksTTL.calledOnceWith("123", 1 * 86400)).to.be.true; + expect(res.status.calledOnceWith(200)).to.be.true; + expect( + res.json.calledOnceWith({ + success: true, + msg: successMessages.CHECK_UPDATE_TTL, + }) + ).to.be.true; + }); }); diff --git a/Server/tests/controllers/controllerUtils.test.js b/Server/tests/controllers/controllerUtils.test.js index 55c16b857..7f5d9b449 100644 --- a/Server/tests/controllers/controllerUtils.test.js +++ b/Server/tests/controllers/controllerUtils.test.js @@ -6,6 +6,9 @@ import { fetchMonitorCertificate, } from "../../controllers/controllerUtils.js"; import { expect } from "chai"; +import sslChecker from "ssl-checker"; +import { afterEach } from "node:test"; +import exp from "constants"; describe("controllerUtils - handleValidationError", () => { it("should set status to 422", () => { @@ -109,102 +112,40 @@ describe("controllerUtils - handleError", () => { }); describe("controllerUtils - fetchMonitorCertificate", () => { - const originalTls = { - connect: sinon.stub().callsFake((options, callback) => { - // Create socket stub with sinon stubs for all methods - socket = { - getPeerX509Certificate: sinon.stub().returns({ - subject: "CN=fake-cert", - validTo: "Dec 31 23:59:59 2023 GMT", - }), - end: sinon.stub(), - on: sinon.stub(), - }; - - // Use process.nextTick to ensure async behavior - process.nextTick(() => { - callback.call(socket); // Ensure correct 'this' binding - }); - - return socket; - }), - }; - - let tls, monitor, socket; + let sslChecker, monitor; beforeEach(() => { - monitor = { url: "https://www.google.com" }; - tls = { - connect: sinon.stub().callsFake((options, callback) => { - // Create socket stub with sinon stubs for all methods - socket = { - getPeerX509Certificate: sinon.stub().returns({ - subject: "CN=fake-cert", - validTo: "Dec 31 23:59:59 2023 GMT", - }), - end: sinon.stub(), - on: sinon.stub(), - }; - - // Use process.nextTick to ensure async behavior - process.nextTick(() => { - callback.call(socket); // Ensure correct 'this' binding - }); - - return socket; - }), + monitor = { + url: "https://www.google.com", }; + sslChecker = sinon.stub(); }); afterEach(() => { - tls = { ...originalTls }; sinon.restore(); }); - it("should resolve with the certificate when the connection is successful", async () => { - const certificate = await fetchMonitorCertificate(tls, monitor); - expect(certificate.validTo).to.equal("Dec 31 23:59:59 2023 GMT"); - expect(socket.end.calledOnce).to.be.true; - }); - - it("should reject with an error when the connection fails", async () => { - tls.connect = sinon.stub().throws(new Error("Connection error")); + it("should reject with an error if a URL does not parse", async () => { + monitor.url = "invalidurl"; try { - await fetchMonitorCertificate(tls, monitor); - } catch (error) { - expect(error.message).to.equal("Connection error"); - } - }); - - it("should reject with an error if monitorURL is invalid", async () => { - monitor.url = "invalid-url"; - try { - await fetchMonitorCertificate(tls, monitor); + await fetchMonitorCertificate(sslChecker, monitor); } catch (error) { + expect(error).to.be.an("error"); expect(error.message).to.equal("Invalid URL"); } }); - it("should do a thing", async () => { - tls = { - connect: sinon.stub().callsFake((options, callback) => { - // Create socket stub with sinon stubs for all methods - socket = { - getPeerX509Certificate: sinon.stub().throws(new Error("Certificate error")), - end: sinon.stub(), - on: sinon.stub(), - }; - // Use process.nextTick to ensure async behavior - process.nextTick(() => { - callback.call(socket); // Ensure correct 'this' binding - }); - - return socket; - }), - }; + it("should reject with an error if sslChecker throws an error", async () => { + sslChecker.rejects(new Error("Test error")); try { - await fetchMonitorCertificate(tls, monitor); + await fetchMonitorCertificate(sslChecker, monitor); } catch (error) { - expect(error.message).to.equal("Certificate error"); + expect(error).to.be.an("error"); + expect(error.message).to.equal("Test error"); } }); + it("should return a certificate if sslChecker resolves", async () => { + sslChecker.resolves({ validTo: "2022-01-01" }); + const result = await fetchMonitorCertificate(sslChecker, monitor); + expect(result).to.deep.equal({ validTo: "2022-01-01" }); + }); }); diff --git a/Server/tests/controllers/inviteController.test.js b/Server/tests/controllers/inviteController.test.js index d08d4ec78..13e5ede51 100644 --- a/Server/tests/controllers/inviteController.test.js +++ b/Server/tests/controllers/inviteController.test.js @@ -1,203 +1,203 @@ import { - issueInvitation, - inviteVerifyController, + issueInvitation, + inviteVerifyController, } from "../../controllers/inviteController.js"; import jwt from "jsonwebtoken"; import sinon from "sinon"; import joi from "joi"; describe("inviteController - issueInvitation", () => { - let req, res, next, stub; - beforeEach(() => { - req = { - headers: { authorization: "Bearer token" }, - body: { - email: "test@test.com", - role: ["admin"], - teamId: "123", - }, - db: { requestInviteToken: sinon.stub() }, - settingsService: { getSettings: sinon.stub() }, - emailService: { buildAndSendEmail: sinon.stub() }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); + let req, res, next, stub; + beforeEach(() => { + req = { + headers: { authorization: "Bearer token" }, + body: { + email: "test@test.com", + role: ["admin"], + teamId: "123", + }, + db: { requestInviteToken: sinon.stub() }, + settingsService: { getSettings: sinon.stub() }, + emailService: { buildAndSendEmail: sinon.stub() }, + }; + res = { + status: sinon.stub().returnsThis(), + json: sinon.stub(), + }; + next = sinon.stub(); + }); - afterEach(() => { - sinon.restore(); - }); + afterEach(() => { + sinon.restore(); + }); - it("should reject with an error if role validation fails", async () => { - stub = sinon.stub(jwt, "decode").callsFake(() => { - return { role: ["bad_role"], firstname: "first_name", teamId: "1" }; - }); - await issueInvitation(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0]).to.be.instanceOf(joi.ValidationError); - expect(next.firstCall.args[0].status).to.equal(422); - stub.restore(); - }); + it("should reject with an error if role validation fails", async () => { + stub = sinon.stub(jwt, "decode").callsFake(() => { + return { role: ["bad_role"], firstname: "first_name", teamId: "1" }; + }); + await issueInvitation(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0]).to.be.instanceOf(joi.ValidationError); + expect(next.firstCall.args[0].status).to.equal(422); + stub.restore(); + }); - it("should reject with an error if body validation fails", async () => { - stub = sinon.stub(jwt, "decode").callsFake(() => { - return { role: ["admin"], firstname: "first_name", teamId: "1" }; - }); - req.body = {}; - await issueInvitation(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - stub.restore(); - }); + it("should reject with an error if body validation fails", async () => { + stub = sinon.stub(jwt, "decode").callsFake(() => { + return { role: ["admin"], firstname: "first_name", teamId: "1" }; + }); + req.body = {}; + await issueInvitation(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].status).to.equal(422); + stub.restore(); + }); - it("should reject with an error if DB operations fail", async () => { - stub = sinon.stub(jwt, "decode").callsFake(() => { - return { role: ["admin"], firstname: "first_name", teamId: "1" }; - }); - req.db.requestInviteToken.throws(new Error("DB error")); - await issueInvitation(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - stub.restore(); - }); + it("should reject with an error if DB operations fail", async () => { + stub = sinon.stub(jwt, "decode").callsFake(() => { + return { role: ["admin"], firstname: "first_name", teamId: "1" }; + }); + req.db.requestInviteToken.throws(new Error("DB error")); + await issueInvitation(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].message).to.equal("DB error"); + stub.restore(); + }); - it("should send an invite successfully", async () => { - const token = "token"; - const decodedToken = { - role: "admin", - firstname: "John", - teamId: "team123", - }; - const inviteToken = { token: "inviteToken" }; - const clientHost = "http://localhost"; + it("should send an invite successfully", async () => { + const token = "token"; + const decodedToken = { + role: "admin", + firstname: "John", + teamId: "team123", + }; + const inviteToken = { token: "inviteToken" }; + const clientHost = "http://localhost"; - stub = sinon.stub(jwt, "decode").callsFake(() => { - return decodedToken; - }); - req.db.requestInviteToken.resolves(inviteToken); - req.settingsService.getSettings.returns({ clientHost }); - req.emailService.buildAndSendEmail.resolves(); - await issueInvitation(req, res, next); - expect(res.status.calledWith(200)).to.be.true; - expect( - res.json.calledWith({ - success: true, - msg: "Invite sent", - data: inviteToken, - }) - ).to.be.true; - stub.restore(); - }); + stub = sinon.stub(jwt, "decode").callsFake(() => { + return decodedToken; + }); + req.db.requestInviteToken.resolves(inviteToken); + req.settingsService.getSettings.returns({ clientHost }); + req.emailService.buildAndSendEmail.resolves(); + await issueInvitation(req, res, next); + expect(res.status.calledWith(200)).to.be.true; + expect( + res.json.calledWith({ + success: true, + msg: "Invite sent", + data: inviteToken, + }) + ).to.be.true; + stub.restore(); + }); - it("should send an email successfully", async () => { - const token = "token"; - const decodedToken = { - role: "admin", - firstname: "John", - teamId: "team123", - }; - const inviteToken = { token: "inviteToken" }; - const clientHost = "http://localhost"; + it("should send an email successfully", async () => { + const token = "token"; + const decodedToken = { + role: "admin", + firstname: "John", + teamId: "team123", + }; + const inviteToken = { token: "inviteToken" }; + const clientHost = "http://localhost"; - stub = sinon.stub(jwt, "decode").callsFake(() => { - return decodedToken; - }); - req.db.requestInviteToken.resolves(inviteToken); - req.settingsService.getSettings.returns({ clientHost }); - req.emailService.buildAndSendEmail.resolves(); + stub = sinon.stub(jwt, "decode").callsFake(() => { + return decodedToken; + }); + req.db.requestInviteToken.resolves(inviteToken); + req.settingsService.getSettings.returns({ clientHost }); + req.emailService.buildAndSendEmail.resolves(); - await issueInvitation(req, res, next); - expect(req.emailService.buildAndSendEmail.calledOnce).to.be.true; - expect( - req.emailService.buildAndSendEmail.calledWith( - "employeeActivationTemplate", - { - name: "John", - link: "http://localhost/register/inviteToken", - }, - "test@test.com", - "Welcome to Uptime Monitor" - ) - ).to.be.true; - stub.restore(); - }); + await issueInvitation(req, res, next); + expect(req.emailService.buildAndSendEmail.calledOnce).to.be.true; + expect( + req.emailService.buildAndSendEmail.calledWith( + "employeeActivationTemplate", + { + name: "John", + link: "http://localhost/register/inviteToken", + }, + "test@test.com", + "Welcome to Uptime Monitor" + ) + ).to.be.true; + stub.restore(); + }); - it("should continue executing if sending an email fails", async () => { - const token = "token"; - req.emailService.buildAndSendEmail.rejects(new Error("Email error")); - const decodedToken = { - role: "admin", - firstname: "John", - teamId: "team123", - }; - const inviteToken = { token: "inviteToken" }; - const clientHost = "http://localhost"; + it("should continue executing if sending an email fails", async () => { + const token = "token"; + req.emailService.buildAndSendEmail.rejects(new Error("Email error")); + const decodedToken = { + role: "admin", + firstname: "John", + teamId: "team123", + }; + const inviteToken = { token: "inviteToken" }; + const clientHost = "http://localhost"; - stub = sinon.stub(jwt, "decode").callsFake(() => { - return decodedToken; - }); - req.db.requestInviteToken.resolves(inviteToken); - req.settingsService.getSettings.returns({ clientHost }); - await issueInvitation(req, res, next); - expect(res.status.calledWith(200)).to.be.true; - expect( - res.json.calledWith({ - success: true, - msg: "Invite sent", - data: inviteToken, - }) - ).to.be.true; - stub.restore(); - }); + stub = sinon.stub(jwt, "decode").callsFake(() => { + return decodedToken; + }); + req.db.requestInviteToken.resolves(inviteToken); + req.settingsService.getSettings.returns({ clientHost }); + await issueInvitation(req, res, next); + expect(res.status.calledWith(200)).to.be.true; + expect( + res.json.calledWith({ + success: true, + msg: "Invite sent", + data: inviteToken, + }) + ).to.be.true; + stub.restore(); + }); }); describe("inviteController - inviteVerifyController", () => { - let req, res, next; - beforeEach(() => { - req = { - body: { token: "token" }, - db: { - getInviteToken: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); + let req, res, next; + beforeEach(() => { + req = { + body: { token: "token" }, + db: { + getInviteToken: sinon.stub(), + }, + }; + res = { + status: sinon.stub().returnsThis(), + json: sinon.stub(), + }; + next = sinon.stub(); + }); - afterEach(() => { - sinon.restore(); - }); + afterEach(() => { + sinon.restore(); + }); - it("should reject with an error if body validation fails", async () => { - req.body = {}; - await inviteVerifyController(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); + it("should reject with an error if body validation fails", async () => { + req.body = {}; + await inviteVerifyController(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].status).to.equal(422); + }); - it("should reject with an error if DB operations fail", async () => { - req.db.getInviteToken.throws(new Error("DB error")); - await inviteVerifyController(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); + it("should reject with an error if DB operations fail", async () => { + req.db.getInviteToken.throws(new Error("DB error")); + await inviteVerifyController(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].message).to.equal("DB error"); + }); - it("should return 200 and invite data when validation and invite retrieval are successful", async () => { - req.db.getInviteToken.resolves({ invite: "data" }); - await inviteVerifyController(req, res, next); - expect(res.status.calledWith(200)).to.be.true; - expect( - res.json.calledWith({ - status: "success", - msg: "Invite verified", - data: { invite: "data" }, - }) - ).to.be.true; - expect(next.called).to.be.false; - }); + it("should return 200 and invite data when validation and invite retrieval are successful", async () => { + req.db.getInviteToken.resolves({ invite: "data" }); + await inviteVerifyController(req, res, next); + expect(res.status.calledWith(200)).to.be.true; + expect( + res.json.calledWith({ + status: "success", + msg: "Invite verified", + data: { invite: "data" }, + }) + ).to.be.true; + expect(next.called).to.be.false; + }); }); diff --git a/Server/tests/controllers/maintenanceWindowController.test.js b/Server/tests/controllers/maintenanceWindowController.test.js index 2c24c0f22..92bab9a77 100644 --- a/Server/tests/controllers/maintenanceWindowController.test.js +++ b/Server/tests/controllers/maintenanceWindowController.test.js @@ -1,10 +1,10 @@ import { - createMaintenanceWindows, - getMaintenanceWindowById, - getMaintenanceWindowsByTeamId, - getMaintenanceWindowsByMonitorId, - deleteMaintenanceWindow, - editMaintenanceWindow, + createMaintenanceWindows, + getMaintenanceWindowById, + getMaintenanceWindowsByTeamId, + getMaintenanceWindowsByMonitorId, + deleteMaintenanceWindow, + editMaintenanceWindow, } from "../../controllers/maintenanceWindowController.js"; import jwt from "jsonwebtoken"; @@ -12,405 +12,399 @@ import { successMessages } from "../../utils/messages.js"; import sinon from "sinon"; describe("maintenanceWindowController - createMaintenanceWindows", () => { - let req, res, next, stub; - beforeEach(() => { - req = { - body: { - monitors: ["66ff52e7c5911c61698ac724"], - name: "window", - active: true, - start: "2024-10-11T05:27:13.747Z", - end: "2024-10-11T05:27:14.747Z", - repeat: "123", - }, - headers: { - authorization: "Bearer token", - }, - settingsService: { - getSettings: sinon.stub().returns({ jwtSecret: "jwtSecret" }), - }, - db: { - createMaintenanceWindow: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); + let req, res, next, stub; + beforeEach(() => { + req = { + body: { + monitors: ["66ff52e7c5911c61698ac724"], + name: "window", + active: true, + start: "2024-10-11T05:27:13.747Z", + end: "2024-10-11T05:27:14.747Z", + repeat: "123", + }, + headers: { + authorization: "Bearer token", + }, + settingsService: { + getSettings: sinon.stub().returns({ jwtSecret: "jwtSecret" }), + }, + db: { + createMaintenanceWindow: sinon.stub(), + }, + }; + res = { + status: sinon.stub().returnsThis(), + json: sinon.stub(), + }; + next = sinon.stub(); + }); - afterEach(() => { - sinon.restore(); - }); + afterEach(() => { + sinon.restore(); + }); - it("should reject with an error if body validation fails", async () => { - stub = sinon.stub(jwt, "verify").callsFake(() => { - return { teamId: "123" }; - }); - req.body = {}; - await createMaintenanceWindows(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - stub.restore(); - }); + it("should reject with an error if body validation fails", async () => { + stub = sinon.stub(jwt, "verify").callsFake(() => { + return { teamId: "123" }; + }); + req.body = {}; + await createMaintenanceWindows(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].status).to.equal(422); + stub.restore(); + }); - it("should reject with an error if jwt.verify fails", async () => { - stub = sinon.stub(jwt, "verify").throws(new jwt.JsonWebTokenError()); - await createMaintenanceWindows(req, res, next); - expect(next.firstCall.args[0]).to.be.instanceOf(jwt.JsonWebTokenError); - stub.restore(); - }); + it("should reject with an error if jwt.verify fails", async () => { + stub = sinon.stub(jwt, "verify").throws(new jwt.JsonWebTokenError()); + await createMaintenanceWindows(req, res, next); + expect(next.firstCall.args[0]).to.be.instanceOf(jwt.JsonWebTokenError); + stub.restore(); + }); - it("should reject with an error DB operations fail", async () => { - stub = sinon.stub(jwt, "verify").callsFake(() => { - return { teamId: "123" }; - }); - req.db.createMaintenanceWindow.throws(new Error("DB error")); - await createMaintenanceWindows(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - stub.restore(); - }); - it("should return success message if all operations are successful", async () => { - stub = sinon.stub(jwt, "verify").callsFake(() => { - return { teamId: "123" }; - }); - await createMaintenanceWindows(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(201); - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.MAINTENANCE_WINDOW_CREATE, - }) - ).to.be.true; - stub.restore(); - }); - it("should return success message if all operations are successful with active set to undefined", async () => { - req.body.active = undefined; - stub = sinon.stub(jwt, "verify").callsFake(() => { - return { teamId: "123" }; - }); - await createMaintenanceWindows(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(201); - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.MAINTENANCE_WINDOW_CREATE, - }) - ).to.be.true; - stub.restore(); - }); + it("should reject with an error DB operations fail", async () => { + stub = sinon.stub(jwt, "verify").callsFake(() => { + return { teamId: "123" }; + }); + req.db.createMaintenanceWindow.throws(new Error("DB error")); + await createMaintenanceWindows(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].message).to.equal("DB error"); + stub.restore(); + }); + it("should return success message if all operations are successful", async () => { + stub = sinon.stub(jwt, "verify").callsFake(() => { + return { teamId: "123" }; + }); + await createMaintenanceWindows(req, res, next); + expect(res.status.firstCall.args[0]).to.equal(201); + expect( + res.json.calledOnceWith({ + success: true, + msg: successMessages.MAINTENANCE_WINDOW_CREATE, + }) + ).to.be.true; + stub.restore(); + }); + it("should return success message if all operations are successful with active set to undefined", async () => { + req.body.active = undefined; + stub = sinon.stub(jwt, "verify").callsFake(() => { + return { teamId: "123" }; + }); + await createMaintenanceWindows(req, res, next); + expect(res.status.firstCall.args[0]).to.equal(201); + expect( + res.json.calledOnceWith({ + success: true, + msg: successMessages.MAINTENANCE_WINDOW_CREATE, + }) + ).to.be.true; + stub.restore(); + }); }); describe("maintenanceWindowController - getMaintenanceWindowById", () => { - let req, res, next; - beforeEach(() => { - req = { - body: {}, - params: { - id: "123", - }, - headers: { - authorization: "Bearer token", - }, - settingsService: { - getSettings: sinon.stub().returns({ jwtSecret: "jwtSecret" }), - }, - db: { - getMaintenanceWindowById: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); + let req, res, next; + beforeEach(() => { + req = { + body: {}, + params: { + id: "123", + }, + headers: { + authorization: "Bearer token", + }, + settingsService: { + getSettings: sinon.stub().returns({ jwtSecret: "jwtSecret" }), + }, + db: { + getMaintenanceWindowById: sinon.stub(), + }, + }; + res = { + status: sinon.stub().returnsThis(), + json: sinon.stub(), + }; + next = sinon.stub(); + }); - it("should reject if param validation fails", async () => { - req.params = {}; - await getMaintenanceWindowById(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); + it("should reject if param validation fails", async () => { + req.params = {}; + await getMaintenanceWindowById(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].status).to.equal(422); + }); - it("should reject if DB operations fail", async () => { - req.db.getMaintenanceWindowById.throws(new Error("DB error")); - await getMaintenanceWindowById(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); - it("should return success message with data if all operations are successful", async () => { - req.db.getMaintenanceWindowById.returns({ id: "123" }); - await getMaintenanceWindowById(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.MAINTENANCE_WINDOW_GET_BY_ID, - data: { id: "123" }, - }) - ).to.be.true; - }); + it("should reject if DB operations fail", async () => { + req.db.getMaintenanceWindowById.throws(new Error("DB error")); + await getMaintenanceWindowById(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].message).to.equal("DB error"); + }); + it("should return success message with data if all operations are successful", async () => { + req.db.getMaintenanceWindowById.returns({ id: "123" }); + await getMaintenanceWindowById(req, res, next); + expect(res.status.firstCall.args[0]).to.equal(200); + expect( + res.json.calledOnceWith({ + success: true, + msg: successMessages.MAINTENANCE_WINDOW_GET_BY_ID, + data: { id: "123" }, + }) + ).to.be.true; + }); }); describe("maintenanceWindowController - getMaintenanceWindowsByTeamId", () => { - let req, res, next, stub; - beforeEach(() => { - req = { - body: {}, - params: {}, - query: {}, - headers: { - authorization: "Bearer token", - }, - settingsService: { - getSettings: sinon.stub().returns({ jwtSecret: "jwtSecret" }), - }, - db: { - getMaintenanceWindowsByTeamId: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - it("should reject if query validation fails", async () => { - req.query = { - invalid: 1, - }; - await getMaintenanceWindowsByTeamId(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - it("should reject if jwt.verify fails", async () => { - stub = sinon.stub(jwt, "verify").throws(new jwt.JsonWebTokenError()); - await getMaintenanceWindowsByTeamId(req, res, next); - expect(next.firstCall.args[0]).to.be.instanceOf(jwt.JsonWebTokenError); - stub.restore(); - }); + let req, res, next, stub; + beforeEach(() => { + req = { + body: {}, + params: {}, + query: {}, + headers: { + authorization: "Bearer token", + }, + settingsService: { + getSettings: sinon.stub().returns({ jwtSecret: "jwtSecret" }), + }, + db: { + getMaintenanceWindowsByTeamId: sinon.stub(), + }, + }; + res = { + status: sinon.stub().returnsThis(), + json: sinon.stub(), + }; + next = sinon.stub(); + }); + it("should reject if query validation fails", async () => { + req.query = { + invalid: 1, + }; + await getMaintenanceWindowsByTeamId(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].status).to.equal(422); + }); + it("should reject if jwt.verify fails", async () => { + stub = sinon.stub(jwt, "verify").throws(new jwt.JsonWebTokenError()); + await getMaintenanceWindowsByTeamId(req, res, next); + expect(next.firstCall.args[0]).to.be.instanceOf(jwt.JsonWebTokenError); + stub.restore(); + }); - it("should reject with an error if DB operations fail", async () => { - stub = sinon.stub(jwt, "verify").callsFake(() => { - return { teamId: "123" }; - }); - req.db.getMaintenanceWindowsByTeamId.throws(new Error("DB error")); - await getMaintenanceWindowsByTeamId(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - stub.restore(); - }); + it("should reject with an error if DB operations fail", async () => { + stub = sinon.stub(jwt, "verify").callsFake(() => { + return { teamId: "123" }; + }); + req.db.getMaintenanceWindowsByTeamId.throws(new Error("DB error")); + await getMaintenanceWindowsByTeamId(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].message).to.equal("DB error"); + stub.restore(); + }); - it("should return success message with data if all operations are successful", async () => { - stub = sinon.stub(jwt, "verify").callsFake(() => { - return { teamId: "123" }; - }); - req.db.getMaintenanceWindowsByTeamId.returns([{ id: "123" }]); - await getMaintenanceWindowsByTeamId(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.MAINTENANCE_WINDOW_GET_BY_TEAM, - data: [{ id: jwt.verify().teamId }], - }) - ).to.be.true; - stub.restore(); - }); + it("should return success message with data if all operations are successful", async () => { + stub = sinon.stub(jwt, "verify").callsFake(() => { + return { teamId: "123" }; + }); + req.db.getMaintenanceWindowsByTeamId.returns([{ id: "123" }]); + await getMaintenanceWindowsByTeamId(req, res, next); + expect(res.status.firstCall.args[0]).to.equal(200); + expect( + res.json.calledOnceWith({ + success: true, + msg: successMessages.MAINTENANCE_WINDOW_GET_BY_TEAM, + data: [{ id: jwt.verify().teamId }], + }) + ).to.be.true; + stub.restore(); + }); }); describe("maintenanceWindowController - getMaintenanceWindowsByMonitorId", () => { - let req, res, next; - beforeEach(() => { - req = { - body: {}, - params: { - monitorId: "123", - }, - query: {}, - headers: { - authorization: "Bearer token", - }, - settingsService: { - getSettings: sinon.stub().returns({ jwtSecret: "jwtSecret" }), - }, - db: { - getMaintenanceWindowsByMonitorId: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); + let req, res, next; + beforeEach(() => { + req = { + body: {}, + params: { + monitorId: "123", + }, + query: {}, + headers: { + authorization: "Bearer token", + }, + settingsService: { + getSettings: sinon.stub().returns({ jwtSecret: "jwtSecret" }), + }, + db: { + getMaintenanceWindowsByMonitorId: sinon.stub(), + }, + }; + res = { + status: sinon.stub().returnsThis(), + json: sinon.stub(), + }; + next = sinon.stub(); + }); - afterEach(() => { - sinon.restore(); - }); + afterEach(() => { + sinon.restore(); + }); - it("should reject if param validation fails", async () => { - req.params = {}; - await getMaintenanceWindowsByMonitorId(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); + it("should reject if param validation fails", async () => { + req.params = {}; + await getMaintenanceWindowsByMonitorId(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].status).to.equal(422); + }); - it("should reject with an error if DB operations fail", async () => { - req.db.getMaintenanceWindowsByMonitorId.throws(new Error("DB error")); - await getMaintenanceWindowsByMonitorId(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); + it("should reject with an error if DB operations fail", async () => { + req.db.getMaintenanceWindowsByMonitorId.throws(new Error("DB error")); + await getMaintenanceWindowsByMonitorId(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].message).to.equal("DB error"); + }); - it("should return success message with data if all operations are successful", async () => { - const data = [{ monitorId: "123" }]; - req.db.getMaintenanceWindowsByMonitorId.returns(data); - await getMaintenanceWindowsByMonitorId(req, res, next); - expect( - req.db.getMaintenanceWindowsByMonitorId.calledOnceWith( - req.params.monitorId - ) - ); - expect(res.status.firstCall.args[0]).to.equal(200); - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.MAINTENANCE_WINDOW_GET_BY_MONITOR, - data: data, - }) - ).to.be.true; - }); + it("should return success message with data if all operations are successful", async () => { + const data = [{ monitorId: "123" }]; + req.db.getMaintenanceWindowsByMonitorId.returns(data); + await getMaintenanceWindowsByMonitorId(req, res, next); + expect(req.db.getMaintenanceWindowsByMonitorId.calledOnceWith(req.params.monitorId)); + expect(res.status.firstCall.args[0]).to.equal(200); + expect( + res.json.calledOnceWith({ + success: true, + msg: successMessages.MAINTENANCE_WINDOW_GET_BY_MONITOR, + data: data, + }) + ).to.be.true; + }); }); describe("maintenanceWindowController - deleteMaintenanceWindow", () => { - let req, res, next; - beforeEach(() => { - req = { - body: {}, - params: { - id: "123", - }, - query: {}, - headers: { - authorization: "Bearer token", - }, - settingsService: { - getSettings: sinon.stub().returns({ jwtSecret: "jwtSecret" }), - }, - db: { - deleteMaintenanceWindowById: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - afterEach(() => { - sinon.restore(); - }); + let req, res, next; + beforeEach(() => { + req = { + body: {}, + params: { + id: "123", + }, + query: {}, + headers: { + authorization: "Bearer token", + }, + settingsService: { + getSettings: sinon.stub().returns({ jwtSecret: "jwtSecret" }), + }, + db: { + deleteMaintenanceWindowById: sinon.stub(), + }, + }; + res = { + status: sinon.stub().returnsThis(), + json: sinon.stub(), + }; + next = sinon.stub(); + }); + afterEach(() => { + sinon.restore(); + }); - it("should reject if param validation fails", async () => { - req.params = {}; - await deleteMaintenanceWindow(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); + it("should reject if param validation fails", async () => { + req.params = {}; + await deleteMaintenanceWindow(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].status).to.equal(422); + }); - it("should reject with an error if DB operations fail", async () => { - req.db.deleteMaintenanceWindowById.throws(new Error("DB error")); - await deleteMaintenanceWindow(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); + it("should reject with an error if DB operations fail", async () => { + req.db.deleteMaintenanceWindowById.throws(new Error("DB error")); + await deleteMaintenanceWindow(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].message).to.equal("DB error"); + }); - it("should return success message if all operations are successful", async () => { - await deleteMaintenanceWindow(req, res, next); - expect(req.db.deleteMaintenanceWindowById.calledOnceWith(req.params.id)); - expect(res.status.firstCall.args[0]).to.equal(200); - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.MAINTENANCE_WINDOW_DELETE, - }) - ).to.be.true; - }); + it("should return success message if all operations are successful", async () => { + await deleteMaintenanceWindow(req, res, next); + expect(req.db.deleteMaintenanceWindowById.calledOnceWith(req.params.id)); + expect(res.status.firstCall.args[0]).to.equal(200); + expect( + res.json.calledOnceWith({ + success: true, + msg: successMessages.MAINTENANCE_WINDOW_DELETE, + }) + ).to.be.true; + }); }); describe("maintenanceWindowController - editMaintenanceWindow", () => { - let req, res, next; - beforeEach(() => { - req = { - body: { - active: true, - name: "test", - }, - params: { - id: "123", - }, - query: {}, - headers: { - authorization: "Bearer token", - }, - settingsService: { - getSettings: sinon.stub().returns({ jwtSecret: "jwtSecret" }), - }, - db: { - editMaintenanceWindowById: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); + let req, res, next; + beforeEach(() => { + req = { + body: { + active: true, + name: "test", + }, + params: { + id: "123", + }, + query: {}, + headers: { + authorization: "Bearer token", + }, + settingsService: { + getSettings: sinon.stub().returns({ jwtSecret: "jwtSecret" }), + }, + db: { + editMaintenanceWindowById: sinon.stub(), + }, + }; + res = { + status: sinon.stub().returnsThis(), + json: sinon.stub(), + }; + next = sinon.stub(); + }); - afterEach(() => { - sinon.restore(); - }); + afterEach(() => { + sinon.restore(); + }); - it("should reject if param validation fails", async () => { - req.params = {}; - await editMaintenanceWindow(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); + it("should reject if param validation fails", async () => { + req.params = {}; + await editMaintenanceWindow(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].status).to.equal(422); + }); - it("should reject if body validation fails", async () => { - req.body = { invalid: 1 }; - await editMaintenanceWindow(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); + it("should reject if body validation fails", async () => { + req.body = { invalid: 1 }; + await editMaintenanceWindow(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].status).to.equal(422); + }); - it("should reject with an error if DB operations fail", async () => { - req.db.editMaintenanceWindowById.throws(new Error("DB error")); - await editMaintenanceWindow(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("DB error"); - }); + it("should reject with an error if DB operations fail", async () => { + req.db.editMaintenanceWindowById.throws(new Error("DB error")); + await editMaintenanceWindow(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].message).to.equal("DB error"); + }); - it("should return success message with data if all operations are successful", async () => { - const data = { id: "123" }; - req.db.editMaintenanceWindowById.returns(data); + it("should return success message with data if all operations are successful", async () => { + const data = { id: "123" }; + req.db.editMaintenanceWindowById.returns(data); - await editMaintenanceWindow(req, res, next); - expect( - req.db.editMaintenanceWindowById.calledOnceWith(req.params.id, req.body) - ); - expect(res.status.firstCall.args[0]).to.equal(200); - expect( - res.json.calledOnceWith({ - success: true, - msg: successMessages.MAINTENANCE_WINDOW_EDIT, - data: data, - }) - ).to.be.true; - }); + await editMaintenanceWindow(req, res, next); + expect(req.db.editMaintenanceWindowById.calledOnceWith(req.params.id, req.body)); + expect(res.status.firstCall.args[0]).to.equal(200); + expect( + res.json.calledOnceWith({ + success: true, + msg: successMessages.MAINTENANCE_WINDOW_EDIT, + data: data, + }) + ).to.be.true; + }); }); diff --git a/Server/tests/controllers/monitorController.test.js b/Server/tests/controllers/monitorController.test.js index b14626204..0a9dbafe6 100644 --- a/Server/tests/controllers/monitorController.test.js +++ b/Server/tests/controllers/monitorController.test.js @@ -6,6 +6,7 @@ import { getMonitorsAndSummaryByTeamId, getMonitorsByTeamId, createMonitor, + checkEndpointResolution, deleteMonitor, deleteAllMonitors, editMonitor, @@ -16,9 +17,7 @@ import jwt from "jsonwebtoken"; import sinon from "sinon"; import { successMessages } from "../../utils/messages.js"; import logger from "../../utils/logger.js"; -import * as monitorController from "../../controllers/monitorController.js"; -import { fetchMonitorCertificate } from "../../controllers/controllerUtils.js"; - +import dns from "dns"; const SERVICE_NAME = "monitorController"; describe("Monitor Controller - getAllMonitors", () => { @@ -460,6 +459,50 @@ describe("Monitor Controller - createMonitor", () => { }); }); +describe("Monitor Controllor - checkEndpointResolution", () => { + let req, res, next, dnsResolveStub; + beforeEach(() => { + req = { query: { monitorURL: 'https://example.com' } }; + res = { status: sinon.stub().returnsThis(), json: sinon.stub() }; + next = sinon.stub(); + dnsResolveStub = sinon.stub(dns, 'resolve'); + }); + afterEach(() => { + dnsResolveStub.restore(); + }); + it('should resolve the URL successfully', async () => { + dnsResolveStub.callsFake((hostname, callback) => callback(null)); + await checkEndpointResolution(req, res, next); + expect(res.status.calledWith(200)).to.be.true; + expect(res.json.calledWith({ + success: true, + msg: 'URL resolved successfully', + })).to.be.true; + expect(next.called).to.be.false; + }); + it("should return an error if DNS resolution fails", async () => { + const dnsError = new Error("DNS resolution failed"); + dnsError.code = 'ENOTFOUND'; + dnsResolveStub.callsFake((hostname, callback) => callback(dnsError)); + await checkEndpointResolution(req, res, next); + expect(next.calledOnce).to.be.true; + const errorPassedToNext = next.getCall(0).args[0]; + expect(errorPassedToNext).to.be.an.instanceOf(Error); + expect(errorPassedToNext.message).to.include('DNS resolution failed'); + expect(errorPassedToNext.code).to.equal('ENOTFOUND'); + expect(errorPassedToNext.status).to.equal(500); + }); + it('should reject with an error if query validation fails', async () => { + req.query.monitorURL = 'invalid-url'; + await checkEndpointResolution(req, res, next); + expect(next.calledOnce).to.be.true; + const error = next.getCall(0).args[0]; + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].status).to.equal(422); + expect(error.message).to.equal('"monitorURL" must be a valid uri'); + }); +}); + describe("Monitor Controller - deleteMonitor", () => { let req, res, next; beforeEach(() => { diff --git a/Server/tests/controllers/queueController.test.js b/Server/tests/controllers/queueController.test.js index b3f3ac63a..f9bd46ea1 100644 --- a/Server/tests/controllers/queueController.test.js +++ b/Server/tests/controllers/queueController.test.js @@ -1,166 +1,166 @@ import { afterEach } from "node:test"; import { - getMetrics, - getJobs, - addJob, - obliterateQueue, + getMetrics, + getJobs, + addJob, + obliterateQueue, } from "../../controllers/queueController.js"; import { successMessages } from "../../utils/messages.js"; import sinon from "sinon"; describe("Queue Controller - getMetrics", () => { - let req, res, next; - beforeEach(() => { - req = { - headers: {}, - params: {}, - body: {}, - db: {}, - jobQueue: { - getMetrics: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - afterEach(() => { - sinon.restore(); - }); - it("should throw an error if getMetrics throws an error", async () => { - req.jobQueue.getMetrics.throws(new Error("getMetrics error")); - await getMetrics(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("getMetrics error"); - }); + let req, res, next; + beforeEach(() => { + req = { + headers: {}, + params: {}, + body: {}, + db: {}, + jobQueue: { + getMetrics: sinon.stub(), + }, + }; + res = { + status: sinon.stub().returnsThis(), + json: sinon.stub(), + }; + next = sinon.stub(); + }); + afterEach(() => { + sinon.restore(); + }); + it("should throw an error if getMetrics throws an error", async () => { + req.jobQueue.getMetrics.throws(new Error("getMetrics error")); + await getMetrics(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].message).to.equal("getMetrics error"); + }); - it("should return a success message and data if getMetrics is successful", async () => { - const data = { data: "metrics" }; - req.jobQueue.getMetrics.returns(data); - await getMetrics(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect(res.json.firstCall.args[0]).to.deep.equal({ - success: true, - msg: successMessages.QUEUE_GET_METRICS, - data, - }); - }); + it("should return a success message and data if getMetrics is successful", async () => { + const data = { data: "metrics" }; + req.jobQueue.getMetrics.returns(data); + await getMetrics(req, res, next); + expect(res.status.firstCall.args[0]).to.equal(200); + expect(res.json.firstCall.args[0]).to.deep.equal({ + success: true, + msg: successMessages.QUEUE_GET_METRICS, + data, + }); + }); }); describe("Queue Controller - getJobs", () => { - let req, res, next; - beforeEach(() => { - req = { - headers: {}, - params: {}, - body: {}, - db: {}, - jobQueue: { - getJobStats: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - afterEach(() => { - sinon.restore(); - }); - it("should reject with an error if getJobs throws an error", async () => { - req.jobQueue.getJobStats.throws(new Error("getJobs error")); - await getJobs(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("getJobs error"); - }); - it("should return a success message and data if getJobs is successful", async () => { - const data = { data: "jobs" }; - req.jobQueue.getJobStats.returns(data); - await getJobs(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect(res.json.firstCall.args[0]).to.deep.equal({ - success: true, - msg: successMessages.QUEUE_GET_METRICS, - data, - }); - }); + let req, res, next; + beforeEach(() => { + req = { + headers: {}, + params: {}, + body: {}, + db: {}, + jobQueue: { + getJobStats: sinon.stub(), + }, + }; + res = { + status: sinon.stub().returnsThis(), + json: sinon.stub(), + }; + next = sinon.stub(); + }); + afterEach(() => { + sinon.restore(); + }); + it("should reject with an error if getJobs throws an error", async () => { + req.jobQueue.getJobStats.throws(new Error("getJobs error")); + await getJobs(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].message).to.equal("getJobs error"); + }); + it("should return a success message and data if getJobs is successful", async () => { + const data = { data: "jobs" }; + req.jobQueue.getJobStats.returns(data); + await getJobs(req, res, next); + expect(res.status.firstCall.args[0]).to.equal(200); + expect(res.json.firstCall.args[0]).to.deep.equal({ + success: true, + msg: successMessages.QUEUE_GET_METRICS, + data, + }); + }); }); describe("Queue Controller - addJob", () => { - let req, res, next; - beforeEach(() => { - req = { - headers: {}, - params: {}, - body: {}, - db: {}, - jobQueue: { - addJob: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - afterEach(() => { - sinon.restore(); - }); - it("should reject with an error if addJob throws an error", async () => { - req.jobQueue.addJob.throws(new Error("addJob error")); - await addJob(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("addJob error"); - }); - it("should return a success message if addJob is successful", async () => { - req.jobQueue.addJob.resolves(); - await addJob(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect(res.json.firstCall.args[0]).to.deep.equal({ - success: true, - msg: successMessages.QUEUE_ADD_JOB, - }); - }); + let req, res, next; + beforeEach(() => { + req = { + headers: {}, + params: {}, + body: {}, + db: {}, + jobQueue: { + addJob: sinon.stub(), + }, + }; + res = { + status: sinon.stub().returnsThis(), + json: sinon.stub(), + }; + next = sinon.stub(); + }); + afterEach(() => { + sinon.restore(); + }); + it("should reject with an error if addJob throws an error", async () => { + req.jobQueue.addJob.throws(new Error("addJob error")); + await addJob(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].message).to.equal("addJob error"); + }); + it("should return a success message if addJob is successful", async () => { + req.jobQueue.addJob.resolves(); + await addJob(req, res, next); + expect(res.status.firstCall.args[0]).to.equal(200); + expect(res.json.firstCall.args[0]).to.deep.equal({ + success: true, + msg: successMessages.QUEUE_ADD_JOB, + }); + }); }); describe("Queue Controller - obliterateQueue", () => { - let req, res, next; - beforeEach(() => { - req = { - headers: {}, - params: {}, - body: {}, - db: {}, - jobQueue: { - obliterate: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - afterEach(() => { - sinon.restore(); - }); - it("should reject with an error if obliterateQueue throws an error", async () => { - req.jobQueue.obliterate.throws(new Error("obliterateQueue error")); - await obliterateQueue(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("obliterateQueue error"); - }); - it("should return a success message if obliterateQueue is successful", async () => { - req.jobQueue.obliterate.resolves(); - await obliterateQueue(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect(res.json.firstCall.args[0]).to.deep.equal({ - success: true, - msg: successMessages.QUEUE_OBLITERATE, - }); - }); + let req, res, next; + beforeEach(() => { + req = { + headers: {}, + params: {}, + body: {}, + db: {}, + jobQueue: { + obliterate: sinon.stub(), + }, + }; + res = { + status: sinon.stub().returnsThis(), + json: sinon.stub(), + }; + next = sinon.stub(); + }); + afterEach(() => { + sinon.restore(); + }); + it("should reject with an error if obliterateQueue throws an error", async () => { + req.jobQueue.obliterate.throws(new Error("obliterateQueue error")); + await obliterateQueue(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].message).to.equal("obliterateQueue error"); + }); + it("should return a success message if obliterateQueue is successful", async () => { + req.jobQueue.obliterate.resolves(); + await obliterateQueue(req, res, next); + expect(res.status.firstCall.args[0]).to.equal(200); + expect(res.json.firstCall.args[0]).to.deep.equal({ + success: true, + msg: successMessages.QUEUE_OBLITERATE, + }); + }); }); diff --git a/Server/tests/controllers/settingsController.test.js b/Server/tests/controllers/settingsController.test.js index c491b22a5..b36addf38 100644 --- a/Server/tests/controllers/settingsController.test.js +++ b/Server/tests/controllers/settingsController.test.js @@ -1,105 +1,103 @@ import { afterEach } from "node:test"; import { - getAppSettings, - updateAppSettings, + getAppSettings, + updateAppSettings, } from "../../controllers/settingsController.js"; import { successMessages } from "../../utils/messages.js"; import sinon from "sinon"; describe("Settings Controller - getAppSettings", () => { - let req, res, next; - beforeEach(() => { - req = { - headers: {}, - params: {}, - body: {}, - db: {}, - settingsService: { - getSettings: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - afterEach(() => { - sinon.restore(); - }); - it("should throw an error if getSettings throws an error", async () => { - req.settingsService.getSettings.throws(new Error("getSettings error")); - await getAppSettings(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("getSettings error"); - }); + let req, res, next; + beforeEach(() => { + req = { + headers: {}, + params: {}, + body: {}, + db: {}, + settingsService: { + getSettings: sinon.stub(), + }, + }; + res = { + status: sinon.stub().returnsThis(), + json: sinon.stub(), + }; + next = sinon.stub(); + }); + afterEach(() => { + sinon.restore(); + }); + it("should throw an error if getSettings throws an error", async () => { + req.settingsService.getSettings.throws(new Error("getSettings error")); + await getAppSettings(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].message).to.equal("getSettings error"); + }); - it("should return a success message and data if getSettings is successful", async () => { - const data = { data: "settings" }; - req.settingsService.getSettings.returns(data); - await getAppSettings(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect(res.json.firstCall.args[0]).to.deep.equal({ - success: true, - msg: successMessages.GET_APP_SETTINGS, - data, - }); - }); + it("should return a success message and data if getSettings is successful", async () => { + const data = { data: "settings" }; + req.settingsService.getSettings.returns(data); + await getAppSettings(req, res, next); + expect(res.status.firstCall.args[0]).to.equal(200); + expect(res.json.firstCall.args[0]).to.deep.equal({ + success: true, + msg: successMessages.GET_APP_SETTINGS, + data, + }); + }); }); describe("Settings Controller - updateAppSettings", () => { - let req, res, next; - beforeEach(() => { - req = { - headers: {}, - params: {}, - body: {}, - db: { - updateAppSettings: sinon.stub(), - }, - settingsService: { - reloadSettings: sinon.stub(), - }, - }; - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - next = sinon.stub(); - }); - afterEach(() => { - sinon.restore(); - }); - it("should reject with an error if body validation fails", async () => { - req.body = { invalid: 1 }; - await updateAppSettings(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].status).to.equal(422); - }); - it("should reject with an error if updateAppSettings throws an error", async () => { - req.db.updateAppSettings.throws(new Error("updateAppSettings error")); - await updateAppSettings(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("updateAppSettings error"); - }); - it("should reject with an error if reloadSettings throws an error", async () => { - req.settingsService.reloadSettings.throws( - new Error("reloadSettings error") - ); - await updateAppSettings(req, res, next); - expect(next.firstCall.args[0]).to.be.an("error"); - expect(next.firstCall.args[0].message).to.equal("reloadSettings error"); - }); - it("should return a success message and data if updateAppSettings is successful", async () => { - const data = { data: "settings" }; - req.settingsService.reloadSettings.returns(data); - await updateAppSettings(req, res, next); - expect(res.status.firstCall.args[0]).to.equal(200); - expect(res.json.firstCall.args[0]).to.deep.equal({ - success: true, - msg: successMessages.UPDATE_APP_SETTINGS, - data, - }); - }); + let req, res, next; + beforeEach(() => { + req = { + headers: {}, + params: {}, + body: {}, + db: { + updateAppSettings: sinon.stub(), + }, + settingsService: { + reloadSettings: sinon.stub(), + }, + }; + res = { + status: sinon.stub().returnsThis(), + json: sinon.stub(), + }; + next = sinon.stub(); + }); + afterEach(() => { + sinon.restore(); + }); + it("should reject with an error if body validation fails", async () => { + req.body = { invalid: 1 }; + await updateAppSettings(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].status).to.equal(422); + }); + it("should reject with an error if updateAppSettings throws an error", async () => { + req.db.updateAppSettings.throws(new Error("updateAppSettings error")); + await updateAppSettings(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].message).to.equal("updateAppSettings error"); + }); + it("should reject with an error if reloadSettings throws an error", async () => { + req.settingsService.reloadSettings.throws(new Error("reloadSettings error")); + await updateAppSettings(req, res, next); + expect(next.firstCall.args[0]).to.be.an("error"); + expect(next.firstCall.args[0].message).to.equal("reloadSettings error"); + }); + it("should return a success message and data if updateAppSettings is successful", async () => { + const data = { data: "settings" }; + req.settingsService.reloadSettings.returns(data); + await updateAppSettings(req, res, next); + expect(res.status.firstCall.args[0]).to.equal(200); + expect(res.json.firstCall.args[0]).to.deep.equal({ + success: true, + msg: successMessages.UPDATE_APP_SETTINGS, + data, + }); + }); }); diff --git a/Server/tests/services/emailService.test.js b/Server/tests/services/emailService.test.js new file mode 100644 index 000000000..1be064af7 --- /dev/null +++ b/Server/tests/services/emailService.test.js @@ -0,0 +1,218 @@ +import sinon from "sinon"; +import EmailService from "../../service/emailService.js"; + +describe("EmailService - Constructor", () => { + let settingsServiceMock; + let fsMock; + let pathMock; + let compileMock; + let mjml2htmlMock; + let nodemailerMock; + let loggerMock; + + beforeEach(() => { + settingsServiceMock = { + getSettings: sinon.stub().returns({ + systemEmailHost: "smtp.example.com", + systemEmailPort: 465, + systemEmailAddress: "test@example.com", + systemEmailPassword: "password", + }), + }; + + fsMock = { + readFileSync: sinon.stub().returns(""), + }; + + pathMock = { + join: sinon.stub().callsFake((...args) => args.join("/")), + }; + + compileMock = sinon.stub().returns(() => ""); + + mjml2htmlMock = sinon.stub().returns({ html: "" }); + + nodemailerMock = { + createTransport: sinon.stub().returns({ + sendMail: sinon.stub().resolves({ messageId: "12345" }), + }), + }; + + loggerMock = { + error: sinon.stub(), + }; + }); + + afterEach(() => { + sinon.restore(); + }); + + it("should initialize template loaders and email transporter", () => { + const emailService = new EmailService( + settingsServiceMock, + fsMock, + pathMock, + compileMock, + mjml2htmlMock, + nodemailerMock, + loggerMock + ); + + // Verify that the settingsService is assigned correctly + expect(emailService.settingsService).to.equal(settingsServiceMock); + + // Verify that the template loaders are initialized + expect(emailService.templateLookup.welcomeEmailTemplate).to.be.a("function"); + expect(emailService.templateLookup.employeeActivationTemplate).to.be.a("function"); + + // Verify that the email transporter is initialized + expect(nodemailerMock.createTransport.calledOnce).to.be.true; + const emailConfig = nodemailerMock.createTransport.getCall(0).args[0]; + expect(emailConfig).to.deep.equal({ + host: "smtp.example.com", + port: 465, + secure: true, + auth: { + user: "test@example.com", + pass: "password", + }, + }); + }); + + it("should have undefined templates if FS fails", () => { + fsMock = { + readFileSync: sinon.stub().throws(new Error("File read error")), + }; + const emailService = new EmailService( + settingsServiceMock, + fsMock, + pathMock, + compileMock, + mjml2htmlMock, + nodemailerMock, + loggerMock + ); + expect(loggerMock.error.called).to.be.true; + const errorCalls = loggerMock.error.getCalls(); + const errorCall = errorCalls.find( + (call) => call.args[0] === "Error loading Email templates" + ); + expect(errorCall).to.not.be.undefined; + expect(emailService.settingsService).to.equal(settingsServiceMock); + expect(emailService.templateLookup.welcomeEmailTemplate).to.be.undefined; + expect(emailService.templateLookup.employeeActivationTemplate).to.be.undefined; + }); +}); + +describe("EmailService - buildAndSendEmail", () => { + let settingsServiceMock; + let fsMock; + let pathMock; + let compileMock; + let mjml2htmlMock; + let nodemailerMock; + let loggerMock; + let emailService; + beforeEach(() => { + settingsServiceMock = { + getSettings: sinon.stub().returns({ + systemEmailHost: "smtp.example.com", + systemEmailPort: 465, + systemEmailAddress: "test@example.com", + systemEmailPassword: "password", + }), + }; + + fsMock = { + readFileSync: sinon.stub().returns(""), + }; + + pathMock = { + join: sinon.stub().callsFake((...args) => args.join("/")), + }; + + compileMock = sinon.stub().returns(() => ""); + + mjml2htmlMock = sinon.stub().returns({ html: "" }); + + nodemailerMock = { + createTransport: sinon.stub().returns({ + sendMail: sinon.stub().resolves({ messageId: "12345" }), + }), + }; + + loggerMock = { + error: sinon.stub(), + }; + + emailService = new EmailService( + settingsServiceMock, + fsMock, + pathMock, + compileMock, + mjml2htmlMock, + nodemailerMock, + loggerMock + ); + }); + + afterEach(() => { + sinon.restore(); + }); + + it("should build and send email successfully", async () => { + const messageId = await emailService.buildAndSendEmail( + "welcomeEmailTemplate", + {}, + "recipient@example.com", + "Welcome" + ); + + expect(messageId).to.equal("12345"); + expect(nodemailerMock.createTransport().sendMail.calledOnce).to.be.true; + }); + + it("should log error if building HTML fails", async () => { + mjml2htmlMock.throws(new Error("MJML error")); + + const messageId = await emailService.buildAndSendEmail( + "welcomeEmailTemplate", + {}, + "recipient@example.com", + "Welcome" + ); + expect(loggerMock.error.calledOnce).to.be.true; + expect(loggerMock.error.getCall(0).args[0]).to.equal("Error building Email HTML"); + }); + + it("should log error if sending email fails", async () => { + nodemailerMock.createTransport().sendMail.rejects(new Error("SMTP error")); + await emailService.buildAndSendEmail( + "welcomeEmailTemplate", + {}, + "recipient@example.com", + "Welcome" + ); + expect(loggerMock.error.calledOnce).to.be.true; + expect(loggerMock.error.getCall(0).args[0]).to.equal("Error sending Email"); + }); + + it("should log error if both building HTML and sending email fail", async () => { + mjml2htmlMock.throws(new Error("MJML error")); + nodemailerMock.createTransport().sendMail.rejects(new Error("SMTP error")); + + const messageId = await emailService.buildAndSendEmail( + "welcomeEmailTemplate", + {}, + "recipient@example.com", + "Welcome" + ); + + expect(messageId).to.be.undefined; + expect(loggerMock.error.calledTwice).to.be.true; + expect(loggerMock.error.getCall(0).args[0]).to.equal("Error building Email HTML"); + expect(loggerMock.error.getCall(1).args[0]).to.equal("Error sending Email"); + }); + + it("should log an error if buildHtml fails", async () => {}); +}); diff --git a/Server/tests/services/networkService.test.js b/Server/tests/services/networkService.test.js new file mode 100644 index 000000000..5d3e81a22 --- /dev/null +++ b/Server/tests/services/networkService.test.js @@ -0,0 +1,861 @@ +import sinon from "sinon"; +import NetworkService from "../../service/networkService.js"; +import { errorMessages, successMessages } from "../../utils/messages.js"; +import exp from "constants"; +describe("NetworkService - Constructor", function () { + let dbMock, emailServiceMock, axiosMock, pingMock, loggerMock, httpMock; + + beforeEach(function () { + dbMock = sinon.stub(); + emailServiceMock = sinon.stub(); + axiosMock = sinon.stub(); + pingMock = sinon.stub(); + loggerMock = sinon.stub(); + httpMock = sinon.stub(); + }); + + it("should correctly initialize properties", function () { + const networkService = new NetworkService( + dbMock, + emailServiceMock, + axiosMock, + pingMock, + loggerMock, + httpMock + ); + + expect(networkService.db).to.equal(dbMock); + expect(networkService.emailService).to.equal(emailServiceMock); + expect(networkService.TYPE_PING).to.equal("ping"); + expect(networkService.TYPE_HTTP).to.equal("http"); + expect(networkService.TYPE_PAGESPEED).to.equal("pagespeed"); + expect(networkService.SERVICE_NAME).to.equal("NetworkService"); + expect(networkService.NETWORK_ERROR).to.equal(5000); + expect(networkService.axios).to.equal(axiosMock); + expect(networkService.ping).to.equal(pingMock); + expect(networkService.logger).to.equal(loggerMock); + expect(networkService.http).to.equal(httpMock); + }); +}); + +describe("NetworkService - handleNotification", () => { + let dbMock, emailServiceMock, loggerMock, networkService; + + beforeEach(function () { + dbMock = { + getNotificationsByMonitorId: sinon.stub(), + }; + emailServiceMock = { + buildAndSendEmail: sinon.stub().resolves(), + }; + loggerMock = { + error: sinon.stub(), + }; + + networkService = new NetworkService( + dbMock, + emailServiceMock, + null, + null, + loggerMock, + null + ); + }); + + it("should send email notifications when monitor is down", async function () { + const monitor = { _id: "monitor1", name: "Test Monitor", url: "http://test.com" }; + const notifications = [{ type: "email", address: "test@example.com" }]; + dbMock.getNotificationsByMonitorId.resolves(notifications); + + await networkService.handleNotification(monitor, false); + + expect(emailServiceMock.buildAndSendEmail.calledOnce).to.be.true; + expect( + emailServiceMock.buildAndSendEmail.calledWith( + "serverIsDownTemplate", + { monitorName: monitor.name, monitorUrl: monitor.url }, + "test@example.com", + "Monitor Test Monitor is down" + ) + ).to.be.true; + }); + + it("should send email notifications when monitor is up", async function () { + const monitor = { _id: "monitor1", name: "Test Monitor", url: "http://test.com" }; + const notifications = [{ type: "email", address: "test@example.com" }]; + dbMock.getNotificationsByMonitorId.resolves(notifications); + + await networkService.handleNotification(monitor, true); + + expect(emailServiceMock.buildAndSendEmail.calledOnce).to.be.true; + expect( + emailServiceMock.buildAndSendEmail.calledWith( + "serverIsUpTemplate", + { monitorName: monitor.name, monitorUrl: monitor.url }, + "test@example.com", + "Monitor Test Monitor is up" + ) + ).to.be.true; + }); + + it("should log an error if an exception is thrown", async function () { + const monitor = { _id: "monitor1", name: "Test Monitor", url: "http://test.com" }; + dbMock.getNotificationsByMonitorId.rejects(new Error("Database error")); + + await networkService.handleNotification(monitor, true); + + expect(loggerMock.error.calledOnce).to.be.true; + expect( + loggerMock.error.calledWith("Database error", { + method: "handleNotification", + service: networkService.SERVICE_NAME, + monitorId: monitor._id, + }) + ).to.be.true; + }); +}); + +describe("NetworkService - handleStatusUpdate", function () { + let dbMock, + emailServiceMock, + axiosMock, + pingMock, + loggerMock, + httpMock, + networkService, + monitorMock; + + beforeEach(function () { + dbMock = { getMonitorById: sinon.stub() }; + emailServiceMock = sinon.stub(); + axiosMock = sinon.stub(); + pingMock = sinon.stub(); + loggerMock = { error: sinon.stub() }; + httpMock = sinon.stub(); + networkService = new NetworkService( + dbMock, + emailServiceMock, + axiosMock, + pingMock, + loggerMock, + httpMock + ); + monitorMock = { + status: sinon.stub(), + save: sinon.stub(), + }; + }); + + afterEach(function () { + sinon.restore(); + }); + + it("should return if getMonitorById throws an error", async function () { + dbMock.getMonitorById.throws(new Error("Database error")); + + const res = await networkService.handleStatusUpdate( + { data: { _id: "monitor1" } }, + true + ); + expect(res).to.be.undefined; + }); + + it("should log an error if monitor is null", async function () { + dbMock.getMonitorById.resolves(null); + + await networkService.handleStatusUpdate({ data: { _id: "monitor1" } }, true); + + expect(loggerMock.error.calledOnce).to.be.true; + expect(loggerMock.error.calledWith("Null Monitor: monitor1")).to.be.true; + }); + + it("should update monitor status if different", async function () { + monitorMock.status = false; + dbMock.getMonitorById.resolves(monitorMock); + + await networkService.handleStatusUpdate({ data: { _id: "monitor1" } }, true); + + expect(monitorMock.save.calledOnce).to.be.true; + expect(monitorMock.status).to.be.true; + }); + + it("should not update monitor status if same", async function () { + monitorMock.status = true; + dbMock.getMonitorById.resolves(monitorMock); + + await networkService.handleStatusUpdate({ data: { _id: "monitor1" } }, true); + + expect(monitorMock.save.called).to.be.false; + }); + + it("should log an error if exception is thrown during status update", async function () { + monitorMock.status = false; + dbMock.getMonitorById.resolves(monitorMock); + monitorMock.save.rejects(new Error("Save error")); + + await networkService.handleStatusUpdate({ data: { _id: "monitor1" } }, true); + + expect(loggerMock.error.calledOnce).to.be.true; + expect(loggerMock.error.calledWith("Save error")).to.be.true; + }); +}); + +describe("NetworkService - measureResponseTime", () => { + let mockOperation, + dbMock, + emailServiceMock, + axiosMock, + pingMock, + loggerMock, + httpMock, + networkService; + + beforeEach(() => { + mockOperation = sinon.stub(); + dbMock = { getMonitorById: sinon.stub() }; + emailServiceMock = sinon.stub(); + axiosMock = sinon.stub(); + pingMock = sinon.stub(); + loggerMock = { error: sinon.stub() }; + httpMock = sinon.stub(); + networkService = new NetworkService( + dbMock, + emailServiceMock, + axiosMock, + pingMock, + loggerMock, + httpMock + ); + }); + + afterEach(() => { + sinon.restore(); + }); + + it("should return response time and response on successful operation", async () => { + const fakeResponse = { data: "test" }; + mockOperation.resolves(fakeResponse); + + const result = await networkService.measureResponseTime(mockOperation); + expect(result).to.have.property("responseTime").that.is.a("number"); + expect(result).to.have.property("response").that.deep.equals(fakeResponse); + }); + + it("should throw an error with response time and additional properties on operation failure", async () => { + const fakeError = new Error("Operation failed"); + mockOperation.rejects(fakeError); + + try { + await networkService.measureResponseTime(mockOperation); + } catch (error) { + expect(error).to.have.property("responseTime").that.is.a("number"); + expect(error).to.have.property("service", networkService.SERVICE_NAME); + expect(error).to.have.property("method", "measureResponseTime"); + expect(error.message).to.equal("Operation failed"); + } + }); + it("should throw an error with response time set properties on operation failure", async () => { + const fakeError = new Error("Operation failed"); + fakeError.service = "test"; + fakeError.method = "testMethod"; + mockOperation.rejects(fakeError); + + try { + await networkService.measureResponseTime(mockOperation); + } catch (error) { + expect(error).to.have.property("responseTime").that.is.a("number"); + expect(error).to.have.property("service", "test"); + expect(error).to.have.property("method", "testMethod"); + expect(error.message).to.equal("Operation failed"); + } + }); +}); + +describe("networkService - handlePing", () => { + let job, + dbMock, + pingMock, + loggerMock, + httpMock, + networkService, + logAndStoreCheckStub, + handleStatusUpdateStub; + beforeEach(function () { + job = { + data: { + url: "http://example.com", + _id: "12345", + }, + }; + + dbMock = { getMonitorById: sinon.stub() }; + pingMock = { promise: { probe: sinon.stub() } }; + loggerMock = { error: sinon.stub() }; + httpMock = sinon.stub(); + networkService = new NetworkService( + dbMock, + null, + null, + pingMock, + loggerMock, + httpMock + ); + logAndStoreCheckStub = sinon.stub(networkService, "logAndStoreCheck"); + handleStatusUpdateStub = sinon.stub(networkService, "handleStatusUpdate"); + }); + + afterEach(function () { + sinon.restore(); + }); + + it("should handle a successful ping response", async function () { + const response = { alive: true }; + const responseTime = 0; + pingMock.promise.probe.resolves(response); + logAndStoreCheckStub.resolves(); + await networkService.handlePing(job); + expect( + logAndStoreCheckStub.calledOnceWith( + { + monitorId: job.data._id, + status: true, + responseTime, + message: successMessages.PING_SUCCESS, + }, + networkService.db.createCheck + ) + ).to.be.true; + expect(handleStatusUpdateStub.calledOnceWith(job, true)).to.be.true; + }); + it("should handle a successful ping response and isAlive === false", async function () { + const response = { alive: false }; + const responseTime = 0; + pingMock.promise.probe.resolves(response); + logAndStoreCheckStub.resolves(); + + await networkService.handlePing(job); + console.log(logAndStoreCheckStub.getCall(0).args[0]); + expect( + logAndStoreCheckStub.calledOnceWith( + { + monitorId: job.data._id, + status: false, + responseTime, + message: errorMessages.PING_CANNOT_RESOLVE, + }, + networkService.db.createCheck + ) + ).to.be.true; + expect(handleStatusUpdateStub.calledOnceWith(job, false)).to.be.true; + }); + + it("should handle a failed ping response", async function () { + const error = new Error("Ping failed"); + error.responseTime = 0; + + pingMock.promise.probe.rejects(error); + + await networkService.handlePing(job); + + expect(handleStatusUpdateStub.calledOnceWith(job, false)).to.be.true; + }); +}); + +describe("NetworkService - handleHttp", () => { + let networkService, + axiosMock, + dbMock, + httpMock, + jobMock, + loggerMock, + logAndStoreCheckStub, + handleStatusUpdateStub; + + beforeEach(() => { + axiosMock = { + get: sinon.stub(), + }; + dbMock = { + createCheck: sinon.stub().resolves(), + }; + httpMock = { + STATUS_CODES: { + 200: "OK", + 500: "Internal Server Error", + }, + }; + loggerMock = { + error: sinon.stub(), + }; + networkService = new NetworkService( + dbMock, + null, + axiosMock, + null, + loggerMock, + httpMock + ); + jobMock = { + data: { + url: "http://example.com", + _id: "monitorId123", + }, + }; + sinon.stub(networkService, "measureResponseTime").callsFake(async (operation) => { + const response = await operation(); + return { responseTime: 100, response }; + }); + logAndStoreCheckStub = sinon.stub(networkService, "logAndStoreCheck").resolves(); + handleStatusUpdateStub = sinon.stub(networkService, "handleStatusUpdate").resolves(); + }); + + afterEach(() => { + sinon.restore(); + }); + + it("should handle a successful HTTP response", async () => { + const responseMock = { status: 200 }; + axiosMock.get.resolves(responseMock); + + await networkService.handleHttp(jobMock); + + expect(networkService.logAndStoreCheck.calledOnce).to.be.true; + const checkData = networkService.logAndStoreCheck.getCall(0).args[0]; + expect(checkData).to.include({ + monitorId: jobMock.data._id, + status: true, + statusCode: 200, + message: "OK", + }); + expect(networkService.handleStatusUpdate.calledOnceWith(jobMock, true)).to.be.true; + }); + + it("should handle an HTTP error response", async () => { + const errorMock = { response: { status: 500 }, responseTime: 200 }; + axiosMock.get.rejects(errorMock); + + await networkService.handleHttp(jobMock); + + expect(networkService.logAndStoreCheck.calledOnce).to.be.true; + const checkData = networkService.logAndStoreCheck.getCall(0).args[0]; + expect(checkData).to.include({ + monitorId: jobMock.data._id, + status: false, + statusCode: 500, + message: "Internal Server Error", + }); + expect(networkService.handleStatusUpdate.calledOnceWith(jobMock, false)).to.be.true; + }); + it("should handle an HTTP error response with undefined status", async () => { + const errorMock = { response: { status: undefined }, responseTime: 200 }; + axiosMock.get.rejects(errorMock); + + await networkService.handleHttp(jobMock); + + expect(networkService.logAndStoreCheck.calledOnce).to.be.true; + const checkData = networkService.logAndStoreCheck.getCall(0).args[0]; + expect(checkData).to.include({ + monitorId: jobMock.data._id, + status: false, + statusCode: 5000, + message: "Network Error", + }); + expect(networkService.handleStatusUpdate.calledOnceWith(jobMock, false)).to.be.true; + }); +}); + +describe("networkService - handlePagespeed", () => { + let dbMock, + axiosMock, + jobMock, + emailServiceMock, + pingMock, + loggerMock, + httpMock, + networkService, + logAndStoreCheckStub, + handleStatusUpdateStub; + beforeEach(() => { + jobMock = { + data: { + _id: "12345", + url: "http://example.com", + }, + }; + dbMock = { getMonitorById: sinon.stub() }; + axiosMock = { get: sinon.stub() }; + + emailServiceMock = sinon.stub(); + pingMock = { promise: { probe: sinon.stub() } }; + loggerMock = { error: sinon.stub() }; + httpMock = { + STATUS_CODES: { + 200: "OK", + 500: "Internal Server Error", + }, + }; + networkService = new NetworkService( + dbMock, + emailServiceMock, + axiosMock, + pingMock, + loggerMock, + httpMock + ); + logAndStoreCheckStub = sinon.stub(networkService, "logAndStoreCheck").resolves(); + handleStatusUpdateStub = sinon.stub(networkService, "handleStatusUpdate").resolves(); + }); + + afterEach(() => { + sinon.restore(); + }); + + it("should handle a successful PageSpeed response", async () => { + const responseMock = { + status: 200, + data: { + lighthouseResult: { + categories: { + accessibility: { score: 0.9 }, + "best-practices": { score: 0.8 }, + seo: { score: 0.7 }, + performance: { score: 0.6 }, + }, + audits: { + "cumulative-layout-shift": { score: 0.1 }, + "speed-index": { score: 0.2 }, + "first-contentful-paint": { score: 0.3 }, + "largest-contentful-paint": { score: 0.4 }, + "total-blocking-time": { score: 0.5 }, + }, + }, + }, + }; + axiosMock.get.resolves(responseMock); + + await networkService.handlePagespeed(jobMock); + expect(networkService.logAndStoreCheck.calledOnce).to.be.true; + const checkData = networkService.logAndStoreCheck.getCall(0).args[0]; + expect(checkData).to.include({ + monitorId: jobMock.data._id, + status: true, + statusCode: 200, + message: "OK", + accessibility: 90, + bestPractices: 80, + seo: 70, + performance: 60, + }); + expect(networkService.handleStatusUpdate.calledOnceWith(jobMock, true)).to.be.true; + }); + + it("should handle a successful PageSpeed response with missing data", async () => { + const responseMock = { + status: 200, + data: { + lighthouseResult: { + categories: {}, + audits: { + "cumulative-layout-shift": { score: 0.1 }, + "speed-index": { score: 0.2 }, + "first-contentful-paint": { score: 0.3 }, + "largest-contentful-paint": { score: 0.4 }, + "total-blocking-time": { score: 0.5 }, + }, + }, + }, + }; + axiosMock.get.resolves(responseMock); + + await networkService.handlePagespeed(jobMock); + expect(networkService.logAndStoreCheck.calledOnce).to.be.true; + const checkData = networkService.logAndStoreCheck.getCall(0).args[0]; + expect(checkData).to.include({ + monitorId: jobMock.data._id, + status: true, + statusCode: 200, + message: "OK", + accessibility: 0, + bestPractices: 0, + seo: 0, + performance: 0, + }); + expect(networkService.handleStatusUpdate.calledOnceWith(jobMock, true)).to.be.true; + }); + + it("should handle an error PageSpeed response", async () => { + const errorMock = { response: { status: 500 } }; + axiosMock.get.rejects(errorMock); + + await networkService.handlePagespeed(jobMock); + + expect(networkService.logAndStoreCheck.calledOnce).to.be.true; + const checkData = networkService.logAndStoreCheck.getCall(0).args[0]; + expect(checkData).to.include({ + monitorId: jobMock.data._id, + status: false, + statusCode: 500, + message: "Internal Server Error", + accessibility: 0, + bestPractices: 0, + seo: 0, + performance: 0, + }); + expect(networkService.handleStatusUpdate.calledOnceWith(jobMock, false)).to.be.true; + }); + it("should handle an error PageSpeed response with unknown status", async () => { + const errorMock = { response: { status: undefined } }; + axiosMock.get.rejects(errorMock); + + await networkService.handlePagespeed(jobMock); + + expect(networkService.logAndStoreCheck.calledOnce).to.be.true; + const checkData = networkService.logAndStoreCheck.getCall(0).args[0]; + expect(checkData).to.include({ + monitorId: jobMock.data._id, + status: false, + statusCode: 5000, + message: "Network Error", + accessibility: 0, + bestPractices: 0, + seo: 0, + performance: 0, + }); + expect(networkService.handleStatusUpdate.calledOnceWith(jobMock, false)).to.be.true; + }); +}); + +describe("networkService - handleHardware", () => { + let dbMock, + axiosMock, + jobMock, + emailServiceMock, + pingMock, + loggerMock, + httpMock, + networkService, + logAndStoreCheckStub, + handleStatusUpdateStub; + beforeEach(() => { + jobMock = { + data: { + _id: "12345", + url: "http://example.com", + }, + }; + dbMock = { getMonitorById: sinon.stub() }; + axiosMock = { get: sinon.stub() }; + + emailServiceMock = sinon.stub(); + pingMock = { promise: { probe: sinon.stub() } }; + loggerMock = { error: sinon.stub() }; + httpMock = { + STATUS_CODES: { + 200: "OK", + 500: "Internal Server Error", + }, + }; + networkService = new NetworkService( + dbMock, + emailServiceMock, + axiosMock, + pingMock, + loggerMock, + httpMock + ); + logAndStoreCheckStub = sinon.stub(networkService, "logAndStoreCheck").resolves(); + handleStatusUpdateStub = sinon.stub(networkService, "handleStatusUpdate").resolves(); + }); + + afterEach(() => { + sinon.restore(); + }); + + it("should handle a successful Hardware response", async () => { + const responseMock = { + monitorId: jobMock.data._id, + cpu: { + physical_core: 1, + logical_core: 1, + frequency: 266, + temperature: null, + free_percent: null, + usage_percent: null, + }, + memory: { + total_bytes: 4, + available_bytes: 4, + used_bytes: 2, + usage_percent: 0.5, + }, + disk: [ + { + read_speed_bytes: 3, + write_speed_bytes: 3, + total_bytes: 10, + free_bytes: 2, + usage_percent: 0.8, + }, + ], + host: { + os: "Linux", + platform: "Ubuntu", + kernel_version: "24.04", + }, + }; + axiosMock.get.resolves(responseMock); + + await networkService.handleHardware(jobMock); + expect(networkService.logAndStoreCheck.calledOnce).to.be.true; + const hardwareData = networkService.logAndStoreCheck.getCall(0).args[0]; + expect(hardwareData.cpu).to.include({ + ...responseMock.cpu, + }); + expect(networkService.handleStatusUpdate.calledOnceWith(jobMock, true)).to.be.true; + }); + + it("should handle an error Hardware response", async () => { + logAndStoreCheckStub.throws(new Error("Hardware error")); + try { + await networkService.handleHardware(jobMock); + } catch (error) { + expect(error.message).to.equal("Hardware error"); + } + }); +}); + +describe("NetworkService - getStatus", () => { + let dbMock, emailServiceMock, axiosMock, pingMock, loggerMock, httpMock, networkService; + + beforeEach(() => { + dbMock = { getMonitorById: sinon.stub() }; + emailServiceMock = sinon.stub(); + axiosMock = sinon.stub(); + pingMock = sinon.stub(); + loggerMock = { error: sinon.stub() }; + httpMock = sinon.stub(); + networkService = new NetworkService( + dbMock, + emailServiceMock, + axiosMock, + pingMock, + loggerMock, + httpMock + ); + }); + + afterEach(() => { + sinon.restore(); + }); + + it("should return true if the job type is ping and handlePing is successful", async () => { + const job = { data: { type: networkService.TYPE_PING } }; + sinon.stub(networkService, "handlePing").resolves(true); + const result = await networkService.getStatus(job); + expect(result).to.be.true; + }); + it("should return false if the job type is ping and handlePing is not successful", async () => { + const job = { data: { type: networkService.TYPE_PING } }; + sinon.stub(networkService, "handlePing").resolves(false); + const result = await networkService.getStatus(job); + expect(result).to.be.false; + }); + it("should return true if the job type is http and handleHttp is successful", async () => { + const job = { data: { type: networkService.TYPE_HTTP } }; + sinon.stub(networkService, "handleHttp").resolves(true); + const result = await networkService.getStatus(job); + expect(result).to.be.true; + }); + it("should return false if the job type is http and handleHttp is not successful", async () => { + const job = { data: { type: networkService.TYPE_HTTP } }; + sinon.stub(networkService, "handleHttp").resolves(false); + const result = await networkService.getStatus(job); + expect(result).to.be.false; + }); + it("should return true if the job type is pagespeed and handlePagespeed is successful", async () => { + const job = { data: { type: networkService.TYPE_PAGESPEED } }; + sinon.stub(networkService, "handlePagespeed").resolves(true); + const result = await networkService.getStatus(job); + expect(result).to.be.true; + }); + it("should return false if the job type is pagespeed and handlePagespeed is not successful", async () => { + const job = { data: { type: networkService.TYPE_PAGESPEED } }; + sinon.stub(networkService, "handlePagespeed").resolves(false); + const result = await networkService.getStatus(job); + expect(result).to.be.false; + }); + it("should return true if the job type is hardware and handleHardware is successful", async () => { + const job = { data: { type: networkService.TYPE_HARDWARE } }; + sinon.stub(networkService, "handleHardware").resolves(true); + const result = await networkService.getStatus(job); + expect(result).to.be.true; + }); + it("should return false if the job type is hardware and handleHardware is not successful", async () => { + const job = { data: { type: networkService.TYPE_HARDWARE } }; + sinon.stub(networkService, "handleHardware").resolves(false); + const result = await networkService.getStatus(job); + expect(result).to.be.false; + }); + it("should log an error and return false if the job type is unknown", async () => { + const job = { data: { type: "unknown" } }; + const result = await networkService.getStatus(job); + expect(result).to.be.false; + expect(loggerMock.error.calledOnce).to.be.true; + expect(loggerMock.error.calledWith(`Unsupported type: unknown`)).to.be.true; + }); +}); + +describe("NetworkService - logAndStoreCheck", async () => { + let dbMock, emailServiceMock, axiosMock, pingMock, loggerMock, httpMock, networkService; + + beforeEach(() => { + dbMock = { getMonitorById: sinon.stub() }; + emailServiceMock = sinon.stub(); + axiosMock = sinon.stub(); + pingMock = sinon.stub(); + loggerMock = { error: sinon.stub() }; + httpMock = sinon.stub(); + networkService = new NetworkService( + dbMock, + emailServiceMock, + axiosMock, + pingMock, + loggerMock, + httpMock + ); + }); + const data = { monitorId: "12345" }; + const writeToDB = sinon.stub(); + afterEach(() => { + sinon.restore(); + }); + + it("should log an error if `writeToDb` throws an error", async () => { + writeToDB.rejects(new Error("Database error")); + await networkService.logAndStoreCheck(data, writeToDB); + expect(loggerMock.error.calledOnce).to.be.true; + expect(loggerMock.error.calledWith(`Error writing check for ${data.monitorId}`)).to.be + .true; + }); + + it("should return the status of the inserted check if successful", async () => { + writeToDB.resolves({ status: true }); + const result = await networkService.logAndStoreCheck(data, writeToDB); + expect(result).to.be.true; + }); + + it("should thrown an error if the check is not inserted (null)", async () => { + writeToDB.resolves(null); + await networkService.logAndStoreCheck(data, writeToDB); + expect(loggerMock.error.calledOnce).to.be.true; + expect(loggerMock.error.calledWith(`Error writing check for ${data.monitorId}`)).to.be + .true; + }); + it("should thrown an error if the check is not inserted (undefined)", async () => { + writeToDB.resolves(undefined); + await networkService.logAndStoreCheck(data, writeToDB); + expect(loggerMock.error.calledOnce).to.be.true; + expect(loggerMock.error.calledWith(`Error writing check for ${data.monitorId}`)).to.be + .true; + }); +}); diff --git a/Server/tests/services/settingsService.test.js b/Server/tests/services/settingsService.test.js new file mode 100644 index 000000000..3060758f7 --- /dev/null +++ b/Server/tests/services/settingsService.test.js @@ -0,0 +1,134 @@ +import sinon from "sinon"; +import SettingsService from "../../service/settingsService.js"; +import { expect } from "chai"; +import NetworkService from "../../service/networkService.js"; +const SERVICE_NAME = "SettingsService"; + +describe("SettingsService", () => { + let sandbox, mockAppSettings; + beforeEach(function () { + sandbox = sinon.createSandbox(); + sandbox.stub(process.env, "CLIENT_HOST").value("http://localhost"); + sandbox.stub(process.env, "JWT_SECRET").value("secret"); + sandbox.stub(process.env, "REFRESH_TOKEN_SECRET").value("refreshSecret"); + sandbox.stub(process.env, "DB_TYPE").value("postgres"); + sandbox + .stub(process.env, "DB_CONNECTION_STRING") + .value("postgres://user:pass@localhost/db"); + sandbox.stub(process.env, "REDIS_HOST").value("localhost"); + sandbox.stub(process.env, "REDIS_PORT").value("6379"); + sandbox.stub(process.env, "TOKEN_TTL").value("3600"); + sandbox.stub(process.env, "REFRESH_TOKEN_TTL").value("86400"); + sandbox.stub(process.env, "PAGESPEED_API_KEY").value("apiKey"); + sandbox.stub(process.env, "SYSTEM_EMAIL_HOST").value("smtp.mailtrap.io"); + sandbox.stub(process.env, "SYSTEM_EMAIL_PORT").value("2525"); + sandbox.stub(process.env, "SYSTEM_EMAIL_ADDRESS").value("test@example.com"); + sandbox.stub(process.env, "SYSTEM_EMAIL_PASSWORD").value("password"); + }); + + mockAppSettings = { + settingOne: 123, + settingTwo: 456, + }; + + afterEach(function () { + sandbox.restore(); + sinon.restore(); + }); + describe("constructor", () => { + it("should construct a new SettingsService", () => { + const settingsService = new SettingsService(mockAppSettings); + expect(settingsService.appSettings).to.equal(mockAppSettings); + }); + }); + describe("loadSettings", () => { + it("should load settings from DB when environment variables are not set", async () => { + const dbSettings = { logLevel: "debug", apiBaseUrl: "http://localhost" }; + const appSettings = { findOne: sinon.stub().returns(dbSettings) }; + const settingsService = new SettingsService(appSettings); + settingsService.settings = {}; + const result = await settingsService.loadSettings(); + expect(result).to.deep.equal(dbSettings); + }); + it("should throw an error if settings are not found", async function () { + const appSettings = { findOne: sinon.stub().returns(null) }; + const settingsService = new SettingsService(appSettings); + settingsService.settings = null; + + try { + await settingsService.loadSettings(); + } catch (error) { + expect(error.message).to.equal("Settings not found"); + expect(error.service).to.equal(SERVICE_NAME); + expect(error.method).to.equal("loadSettings"); + } + }); + + it("should add its method and service name to error if not present", async () => { + const appSettings = { findOne: sinon.stub().throws(new Error("Test error")) }; + const settingsService = new SettingsService(appSettings); + try { + await settingsService.loadSettings(); + } catch (error) { + expect(error.message).to.equal("Test error"); + expect(error.service).to.equal(SERVICE_NAME); + expect(error.method).to.equal("loadSettings"); + } + }); + it("should not add its method and service name to error if present", async () => { + const error = new Error("Test error"); + error.method = "otherMethod"; + error.service = "OTHER_SERVICE"; + const appSettings = { findOne: sinon.stub().throws(error) }; + const settingsService = new SettingsService(appSettings); + try { + await settingsService.loadSettings(); + } catch (error) { + console.log(error); + expect(error.message).to.equal("Test error"); + expect(error.service).to.equal("OTHER_SERVICE"); + expect(error.method).to.equal("otherMethod"); + } + }); + it("should merge DB settings with environment variables", async function () { + const dbSettings = { logLevel: "debug", apiBaseUrl: "http://localhost" }; + const appSettings = { findOne: sinon.stub().returns(dbSettings) }; + const settingsService = new SettingsService(appSettings); + const result = await settingsService.loadSettings(); + expect(result).to.deep.equal(settingsService.settings); + expect(settingsService.settings.logLevel).to.equal("debug"); + expect(settingsService.settings.apiBaseUrl).to.equal("http://localhost"); + }); + }); + describe("reloadSettings", () => { + it("should call loadSettings", async () => { + const dbSettings = { logLevel: "debug", apiBaseUrl: "http://localhost" }; + const appSettings = { findOne: sinon.stub().returns(dbSettings) }; + const settingsService = new SettingsService(appSettings); + settingsService.settings = {}; + const result = await settingsService.reloadSettings(); + expect(result).to.deep.equal(dbSettings); + }); + }); + describe("getSettings", () => { + it("should return the current settings", () => { + const dbSettings = { logLevel: "debug", apiBaseUrl: "http://localhost" }; + const appSettings = { findOne: sinon.stub().returns(dbSettings) }; + const settingsService = new SettingsService(appSettings); + settingsService.settings = dbSettings; + const result = settingsService.getSettings(); + expect(result).to.deep.equal(dbSettings); + }); + it("should throw an error if settings have not been loaded", () => { + const appSettings = { findOne: sinon.stub().returns(null) }; + const settingsService = new SettingsService(appSettings); + settingsService.settings = null; + + try { + settingsService.getSettings(); + } catch (error) { + expect(error.message).to.equal("Settings have not been loaded"); + } + }); + }); +}); diff --git a/Server/tests/utils/dataUtils.test.js b/Server/tests/utils/dataUtils.test.js index 4af193d74..293291b5e 100644 --- a/Server/tests/utils/dataUtils.test.js +++ b/Server/tests/utils/dataUtils.test.js @@ -62,7 +62,6 @@ describe("calculatePercentile", () => { const percentile = 100; const result = calculatePercentile(checks, percentile); const expected = 50; - console.log(result); expect(result).to.equal(expected); }); }); diff --git a/Server/utils/demoMonitors.json b/Server/utils/demoMonitors.json index 6f57940f2..ffce7d226 100644 --- a/Server/utils/demoMonitors.json +++ b/Server/utils/demoMonitors.json @@ -1,1271 +1,1271 @@ [ - { - "name": "0to255", - "url": "https://0to255.com" - }, - { - "name": "10015.io", - "url": "https://10015.io" - }, - { - "name": "3DIcons", - "url": "https://3dicons.co" - }, - { - "name": "About.me", - "url": "https://about.me" - }, - { - "name": "Alias", - "url": "https://alias.co" - }, - { - "name": "All About Berlin", - "url": "https://allaboutberlin.com" - }, - { - "name": "All Acronyms", - "url": "https://allacronyms.com" - }, - { - "name": "All You Can Read ", - "url": "https://allyoucanread.com" - }, - { - "name": "AllTrails", - "url": "https://alltrails.com" - }, - { - "name": "Anotepad", - "url": "https://anotepad.com" - }, - { - "name": "AnswerSocrates", - "url": "https://answersocrates.com" - }, - { - "name": "AnswerThePublic ", - "url": "https://answerthepublic.com" - }, - { - "name": "Apollo ", - "url": "https://apollo.io" - }, - { - "name": "ArrayList", - "url": "https://arraylist.org" - }, - { - "name": "Ask Difference", - "url": "https://askdifference.com" - }, - { - "name": "Audd.io", - "url": "https://audd.io" - }, - { - "name": "Audiocheck", - "url": "https://audiocheck.net" - }, - { - "name": "Audionautix", - "url": "https://audionautix.com" - }, - { - "name": "Authentic Jobs", - "url": "https://authenticjobs.com" - }, - { - "name": "Behind the Name", - "url": "https://behindthename.com" - }, - { - "name": "Bilim Terimleri", - "url": "https://terimler.org" - }, - { - "name": "BitBof", - "url": "https://bitbof.com" - }, - { - "name": "Blank Page", - "url": "https://blank.page" - }, - { - "name": "Bonanza", - "url": "https://bonanza.com" - }, - { - "name": "BookCrossing", - "url": "https://bookcrossing.com" - }, - { - "name": "Browse AI", - "url": "https://browse.ai" - }, - { - "name": "Bubbl.us", - "url": "https://bubbl.us" - }, - { - "name": "Business Model Toolbox", - "url": "https://bmtoolbox.net" - }, - { - "name": "ByClickDownloader", - "url": "https://byclickdownloader.com" - }, - { - "name": "Calligraphr", - "url": "https://calligraphr.com" - }, - { - "name": "CertificateClaim", - "url": "https://certificateclaim.com" - }, - { - "name": "Chosic", - "url": "https://chosic.com" - }, - { - "name": "ClipDrop", - "url": "https://clipdrop.co" - }, - { - "name": "CloudConvert", - "url": "https://cloudconvert.com" - }, - { - "name": "CodingFont", - "url": "https://codingfont.com" - }, - { - "name": "Color Hunt", - "url": "https://colorhunt.co" - }, - { - "name": "ColorHexa", - "url": "https://colorhexa.com" - }, - { - "name": "Conversion-Tool", - "url": "https://conversion-tool.com" - }, - { - "name": "Cool Startup Jobs", - "url": "https://coolstartupjobs.com" - }, - { - "name": "Coroflot", - "url": "https://coroflot.com" - }, - { - "name": "Corrupt-a-File", - "url": "https://corrupt-a-file.net" - }, - { - "name": "Couchsurfing", - "url": "https://couchsurfing.com" - }, - { - "name": "Countries Been", - "url": "https://countriesbeen.com" - }, - { - "name": "Country Code", - "url": "https://countrycode.org" - }, - { - "name": "Creately", - "url": "https://creately.com" - }, - { - "name": "Creately ", - "url": "https://creately.com" - }, - { - "name": "Crossfade.io", - "url": "https://crossfade.io" - }, - { - "name": "Crunchbase", - "url": "https://crunchbase.com" - }, - { - "name": "CVmkr", - "url": "https://cvwizard.com" - }, - { - "name": "Daily Remote", - "url": "https://dailyremote.com" - }, - { - "name": "David Li", - "url": "https://david.li" - }, - { - "name": "DemandHunt", - "url": "https://demandhunt.com" - }, - { - "name": "Designify", - "url": "https://designify.com" - }, - { - "name": "Diff Checker", - "url": "https://diffchecker.com" - }, - { - "name": "DifferenceBetween.info", - "url": "https://differencebetween.info" - }, - { - "name": "Digital Glossary", - "url": "https://digital-glossary.com" - }, - { - "name": "Dimensions", - "url": "https://dimensions.com" - }, - { - "name": "Discoverify Music", - "url": "https://discoverifymusic.com" - }, - { - "name": "discu.eu", - "url": "https://discu.eu" - }, - { - "name": "Do It Yourself", - "url": "https://doityourself.com" - }, - { - "name": "draw.io", - "url": "https://drawio.com" - }, - { - "name": "Drumeo", - "url": "https://drumeo.com" - }, - { - "name": "Dummies", - "url": "https://dummies.com" - }, - { - "name": "Easel.ly", - "url": "https://easel.ly" - }, - { - "name": "Educalingo", - "url": "https://educalingo.com" - }, - { - "name": "Emoji Combos", - "url": "https://emojicombos.com" - }, - { - "name": "EquityBee", - "url": "https://equitybee.com" - }, - { - "name": "EquityZen", - "url": "https://equityzen.com" - }, - { - "name": "Escape Room Tips", - "url": "https://escaperoomtips.com" - }, - { - "name": "Every Noise", - "url": "https://everynoise.com" - }, - { - "name": "Every Time Zone", - "url": "https://everytimezone.com" - }, - { - "name": "Excalideck", - "url": "https://excalideck.com" - }, - { - "name": "Excalidraw", - "url": "https://excalidraw.com" - }, - { - "name": "Extract pics", - "url": "https://extract.pics" - }, - { - "name": "EZGIF", - "url": "https://ezgif.com" - }, - { - "name": "FactSlides", - "url": "https://factslides.com" - }, - { - "name": "FIGR ", - "url": "https://figr.app" - }, - { - "name": "Fine Dictionary", - "url": "https://finedictionary.com" - }, - { - "name": "Fiverr", - "url": "https://fiverr.com" - }, - { - "name": "Fix It Club", - "url": "https://fixitclub.com" - }, - { - "name": "Flightradar24", - "url": "https://flightradar24.com" - }, - { - "name": "FlowCV ", - "url": "https://flowcv.com" - }, - { - "name": "Font Squirrel", - "url": "https://fontsquirrel.com" - }, - { - "name": "FontAwesome", - "url": "https://fontawesome.com" - }, - { - "name": "Fontello ", - "url": "https://fontello.com" - }, - { - "name": "Form to Chatbot", - "url": "https://formtochatbot.com" - }, - { - "name": "Founder Resources", - "url": "https://founderresources.io" - }, - { - "name": "Franz", - "url": "https://meetfranz.com" - }, - { - "name": "Fraze It", - "url": "https://fraze.it" - }, - { - "name": "Freecycle", - "url": "https://freecycle.org" - }, - { - "name": "FreeType", - "url": "https://freetype.org" - }, - { - "name": "FutureM", - "url": "https://futureme.org" - }, - { - "name": "Generated.Photos", - "url": "https://generated.photos" - }, - { - "name": "Get Human", - "url": "https://gethuman.com" - }, - { - "name": "Go Bento", - "url": "https://gobento.com" - }, - { - "name": "Good CV", - "url": "https://goodcv.com" - }, - { - "name": "Grammar Monster", - "url": "https://grammar-monster.com" - }, - { - "name": "Grammar Book", - "url": "https://grammarbook.com" - }, - { - "name": "Gummy Search", - "url": "https://gummysearch.com" - }, - { - "name": "Gumroad", - "url": "https://gumroad.com" - }, - { - "name": "HealthIcons", - "url": "https://healthicons.org" - }, - { - "name": "HexColor", - "url": "https://hexcolor.co" - }, - { - "name": "Hidden Life Radio", - "url": "https://hiddenliferadio.com" - }, - { - "name": "Hired", - "url": "https://lhh.com" - }, - { - "name": "Honey", - "url": "https://joinhoney.com" - }, - { - "name": "HowStuffWorks", - "url": "https://howstuffworks.com" - }, - { - "name": "HugeIcons Pro", - "url": "https://hugeicons.com" - }, - { - "name": "Humble Bundle", - "url": "https://humblebundle.com" - }, - { - "name": "I Have No TV", - "url": "https://ihavenotv.com" - }, - { - "name": "I Miss the Office", - "url": "https://imisstheoffice.eu" - }, - { - "name": "IcoMoon", - "url": "https://icomoon.io" - }, - { - "name": "Iconfinder", - "url": "https://iconfinder.com" - }, - { - "name": "Icon Packs", - "url": "https://iconpacks.net" - }, - { - "name": "Iconshock", - "url": "https://iconshock.com" - }, - { - "name": "Iconz Design", - "url": "https://iconz.design" - }, - { - "name": "iFixit", - "url": "https://ifixit.com" - }, - { - "name": "IFTTT", - "url": "https://ifttt.com" - }, - { - "name": "Illlustrations", - "url": "https://illlustrations.co" - }, - { - "name": "Illustration Kit", - "url": "https://illustrationkit.com" - }, - { - "name": "IMSDB", - "url": "https://imsdb.com" - }, - { - "name": "Incompetech", - "url": "https://incompetech.com" - }, - { - "name": "Incredibox", - "url": "https://incredibox.com" - }, - { - "name": "InnerBod", - "url": "https://innerbody.com" - }, - { - "name": "Instructables", - "url": "https://instructables.com" - }, - { - "name": "Integromat", - "url": "https://make.com" - }, - { - "name": "Investopedia", - "url": "https://investopedia.com" - }, - { - "name": "Japanese Wiki Corpus", - "url": "https://japanesewiki.com" - }, - { - "name": "Jitter.Video", - "url": "https://jitter.video" - }, - { - "name": "Jobspresso", - "url": "https://jobspresso.co" - }, - { - "name": "JPEG-Optimizer", - "url": "https://jpeg-optimizer.com" - }, - { - "name": "JS Remotely", - "url": "https://jsremotely.com" - }, - { - "name": "JScreenFix", - "url": "https://jscreenfix.com" - }, - { - "name": "JSON Resume", - "url": "https://jsonresume.io" - }, - { - "name": "Just Join", - "url": "https://justjoin.it" - }, - { - "name": "Just the Recipe", - "url": "https://justtherecipe.com" - }, - { - "name": "JustRemote", - "url": "https://justremote.co" - }, - { - "name": "JustWatch", - "url": "https://justwatch.com" - }, - { - "name": "Kanopy", - "url": "https://kanopy.com" - }, - { - "name": "Kassellabs", - "url": "https://kassellabs.io" - }, - { - "name": "Key Differences", - "url": "https://keydifferences.com" - }, - { - "name": "Keybase", - "url": "https://keybase.io" - }, - { - "name": "KeyValues", - "url": "https://keyvalues.com" - }, - { - "name": "KHInsider", - "url": "https://khinsider.com" - }, - { - "name": "Killed by Google", - "url": "https://killedbygoogle.com" - }, - { - "name": "Kimovil", - "url": "https://kimovil.com" - }, - { - "name": "Lalal.ai", - "url": "https://www.lalal.ai" - }, - { - "name": "Learn Anything", - "url": "https://learn-anything.xyz" - }, - { - "name": "LendingTree", - "url": "https://lendingtree.com" - }, - { - "name": "Lightyear.fm", - "url": "https://lightyear.fm" - }, - { - "name": "LittleSis", - "url": "https://littlesis.org" - }, - { - "name": "Looria", - "url": "https://looria.com" - }, - { - "name": "Lucidchart", - "url": "https://lucidchart.com" - }, - { - "name": "Lunar", - "url": "https://lunar.fyi" - }, - { - "name": "Manuals Lib", - "url": "https://manualslib.com" - }, - { - "name": "Map Crunch", - "url": "https://mapcrunch.com" - }, - { - "name": "Masterworks", - "url": "https://masterworks.com" - }, - { - "name": "MediaFire", - "url": "https://mediafire.com" - }, - { - "name": "Mixlr", - "url": "https://mixlr.com" - }, - { - "name": "Moises AI", - "url": "https://moises.ai" - }, - { - "name": "Money", - "url": "https://money.com" - }, - { - "name": "Mountain Project", - "url": "https://mountainproject.com" - }, - { - "name": "Movie Map", - "url": "https://movie-map.com" - }, - { - "name": "Movie Sounds", - "url": "https://movie-sounds.org" - }, - { - "name": "MP3Cut", - "url": "https://mp3cut.net" - }, - { - "name": "Murmel", - "url": "https://murmel.social" - }, - { - "name": "Muscle Wiki", - "url": "https://musclewiki.com" - }, - { - "name": "Music-Map", - "url": "https://music-map.com" - }, - { - "name": "MusicTheory.net", - "url": "https://musictheory.net" - }, - { - "name": "MyFonts", - "url": "https://myfonts.com" - }, - { - "name": "MyFridgeFood", - "url": "https://myfridgefood.com" - }, - { - "name": "Nameberry", - "url": "https://nameberry.com" - }, - { - "name": "Namechk", - "url": "https://namechk.com" - }, - { - "name": "Ncase", - "url": "https://ncase.me" - }, - { - "name": "News in Levels", - "url": "https://newsinlevels.com" - }, - { - "name": "Noisli", - "url": "https://noisli.com" - }, - { - "name": "Notes.io", - "url": "https://notes.io" - }, - { - "name": "Novoresume", - "url": "https://novoresume.com" - }, - { - "name": "Ocoya", - "url": "https://ocoya.com" - }, - { - "name": "Old Computers Museum", - "url": "https://oldcomputers.net" - }, - { - "name": "Online Tone Generator", - "url": "https://onlinetonegenerator.com" - }, - { - "name": "Online-Convert", - "url": "https://online-convert.com" - }, - { - "name": "OnlineConversion", - "url": "https://onlineconversion.com" - }, - { - "name": "Online OCR", - "url": "https://onlineocr.net" - }, - { - "name": "OpenWeatherMap", - "url": "https://openweathermap.org" - }, - { - "name": "OrgPad", - "url": "https://orgpad.com" - }, - { - "name": "Passport Index", - "url": "https://passportindex.org" - }, - { - "name": "PDF Candy", - "url": "https://pdfcandy.com" - }, - { - "name": "PDF2DOC", - "url": "https://pdf2doc.com" - }, - { - "name": "PDFescape", - "url": "https://pdfescape.com" - }, - { - "name": "PfpMaker", - "url": "https://pfpmaker.com" - }, - { - "name": "PIDGI Wiki ", - "url": "https://pidgi.net" - }, - { - "name": "PimEyes", - "url": "https://pimeyes.com" - }, - { - "name": "Pipl ", - "url": "https://pipl.com" - }, - { - "name": "PixelBazaar", - "url": "https://pixelbazaar.com" - }, - { - "name": "PixelPaper", - "url": "https://pixelpaper.io" - }, - { - "name": "Ponly", - "url": "https://ponly.com" - }, - { - "name": "PowerToFly", - "url": "https://powertofly.com" - }, - { - "name": "Pretzel Rocks", - "url": "https://pretzel.rocks" - }, - { - "name": "PrintIt", - "url": "https://printit.work" - }, - { - "name": "Prismatext", - "url": "https://prismatext.com" - }, - { - "name": "Puffin Maps", - "url": "https://puffinmaps.com" - }, - { - "name": "Puzzle Loop ", - "url": "https://puzzle-loop.com" - }, - { - "name": "QuoteMaster", - "url": "https://quotemaster.org" - }, - { - "name": "Radio Garden", - "url": "https://radio.garden" - }, - { - "name": "Radiooooo", - "url": "https://radiooooo.com" - }, - { - "name": "Radiosondy", - "url": "https://radiosondy.info" - }, - { - "name": "Rainy Mood", - "url": "https://rainymood.com" - }, - { - "name": "Random Street View", - "url": "https://randomstreetview.com" - }, - { - "name": "Rap4Ever", - "url": "https://rap4all.com" - }, - { - "name": "RareFilm", - "url": "https://rarefilm.net" - }, - { - "name": "Rattibha", - "url": "https://rattibha.com" - }, - { - "name": "Reddit List ", - "url": "https://redditlist.com" - }, - { - "name": "RedditSearch.io", - "url": "https://redditsearch.io" - }, - { - "name": "Reelgood", - "url": "https://reelgood.com" - }, - { - "name": "Reface", - "url": "https://reface.ai" - }, - { - "name": "Rejected.us", - "url": "https://rejected.us" - }, - { - "name": "Relanote", - "url": "https://relanote.com" - }, - { - "name": "Remote Leaf", - "url": "https://remoteleaf.com" - }, - { - "name": "Remote OK", - "url": "https://remoteok.com" - }, - { - "name": "Remote Starter Kit ", - "url": "https://remotestarterkit.com" - }, - { - "name": "Remote.co", - "url": "https://remote.co" - }, - { - "name": "Remote Base ", - "url": "https://remotebase.com" - }, - { - "name": "Remote Bear", - "url": "https://remotebear.io" - }, - { - "name": "Remove.bg", - "url": "https://remove.bg" - }, - { - "name": "Respresso", - "url": "https://respresso.io" - }, - { - "name": "Reveddit", - "url": "https://reveddit.com" - }, - { - "name": "Rhymer", - "url": "https://rhymer.com" - }, - { - "name": "RhymeZone", - "url": "https://rhymezone.com" - }, - { - "name": "Ribbet", - "url": "https://ribbet.com" - }, - { - "name": "Roadmap.sh", - "url": "https://roadmap.sh" - }, - { - "name": "Roadtrippers", - "url": "https://roadtrippers.com" - }, - { - "name": "RxResu.me", - "url": "https://rxresu.me" - }, - { - "name": "SchemeColor", - "url": "https://schemecolor.com" - }, - { - "name": "Screenshot.Guru", - "url": "https://screenshot.guru" - }, - { - "name": "SeatGuru", - "url": "https://seatguru.com" - }, - { - "name": "Sessions", - "url": "https://sessions.us" - }, - { - "name": "Shottr", - "url": "https://shottr.cc" - }, - { - "name": "Signature Maker", - "url": "https://signature-maker.net" - }, - { - "name": "Skip The Drive", - "url": "https://skipthedrive.com" - }, - { - "name": "Slowby", - "url": "https://slowby.travel" - }, - { - "name": "Small World", - "url": "https://smallworld.kiwi" - }, - { - "name": "SmallPDF", - "url": "https://smallpdf.com" - }, - { - "name": "Social Image Maker", - "url": "https://socialimagemaker.io" - }, - { - "name": "Social Sizes", - "url": "https://socialsizes.io" - }, - { - "name": "SoundLove", - "url": "https://soundlove.se" - }, - { - "name": "Spline", - "url": "https://spline.design" - }, - { - "name": "Starkey Comics", - "url": "https://starkeycomics.com" - }, + { + "name": "0to255", + "url": "https://0to255.com" + }, + { + "name": "10015.io", + "url": "https://10015.io" + }, + { + "name": "3DIcons", + "url": "https://3dicons.co" + }, + { + "name": "About.me", + "url": "https://about.me" + }, + { + "name": "Alias", + "url": "https://alias.co" + }, + { + "name": "All About Berlin", + "url": "https://allaboutberlin.com" + }, + { + "name": "All Acronyms", + "url": "https://allacronyms.com" + }, + { + "name": "All You Can Read ", + "url": "https://allyoucanread.com" + }, + { + "name": "AllTrails", + "url": "https://alltrails.com" + }, + { + "name": "Anotepad", + "url": "https://anotepad.com" + }, + { + "name": "AnswerSocrates", + "url": "https://answersocrates.com" + }, + { + "name": "AnswerThePublic ", + "url": "https://answerthepublic.com" + }, + { + "name": "Apollo ", + "url": "https://apollo.io" + }, + { + "name": "ArrayList", + "url": "https://arraylist.org" + }, + { + "name": "Ask Difference", + "url": "https://askdifference.com" + }, + { + "name": "Audd.io", + "url": "https://audd.io" + }, + { + "name": "Audiocheck", + "url": "https://audiocheck.net" + }, + { + "name": "Audionautix", + "url": "https://audionautix.com" + }, + { + "name": "Authentic Jobs", + "url": "https://authenticjobs.com" + }, + { + "name": "Behind the Name", + "url": "https://behindthename.com" + }, + { + "name": "Bilim Terimleri", + "url": "https://terimler.org" + }, + { + "name": "BitBof", + "url": "https://bitbof.com" + }, + { + "name": "Blank Page", + "url": "https://blank.page" + }, + { + "name": "Bonanza", + "url": "https://bonanza.com" + }, + { + "name": "BookCrossing", + "url": "https://bookcrossing.com" + }, + { + "name": "Browse AI", + "url": "https://browse.ai" + }, + { + "name": "Bubbl.us", + "url": "https://bubbl.us" + }, + { + "name": "Business Model Toolbox", + "url": "https://bmtoolbox.net" + }, + { + "name": "ByClickDownloader", + "url": "https://byclickdownloader.com" + }, + { + "name": "Calligraphr", + "url": "https://calligraphr.com" + }, + { + "name": "CertificateClaim", + "url": "https://certificateclaim.com" + }, + { + "name": "Chosic", + "url": "https://chosic.com" + }, + { + "name": "ClipDrop", + "url": "https://clipdrop.co" + }, + { + "name": "CloudConvert", + "url": "https://cloudconvert.com" + }, + { + "name": "CodingFont", + "url": "https://codingfont.com" + }, + { + "name": "Color Hunt", + "url": "https://colorhunt.co" + }, + { + "name": "ColorHexa", + "url": "https://colorhexa.com" + }, + { + "name": "Conversion-Tool", + "url": "https://conversion-tool.com" + }, + { + "name": "Cool Startup Jobs", + "url": "https://coolstartupjobs.com" + }, + { + "name": "Coroflot", + "url": "https://coroflot.com" + }, + { + "name": "Corrupt-a-File", + "url": "https://corrupt-a-file.net" + }, + { + "name": "Couchsurfing", + "url": "https://couchsurfing.com" + }, + { + "name": "Countries Been", + "url": "https://countriesbeen.com" + }, + { + "name": "Country Code", + "url": "https://countrycode.org" + }, + { + "name": "Creately", + "url": "https://creately.com" + }, + { + "name": "Creately ", + "url": "https://creately.com" + }, + { + "name": "Crossfade.io", + "url": "https://crossfade.io" + }, + { + "name": "Crunchbase", + "url": "https://crunchbase.com" + }, + { + "name": "CVmkr", + "url": "https://cvwizard.com" + }, + { + "name": "Daily Remote", + "url": "https://dailyremote.com" + }, + { + "name": "David Li", + "url": "https://david.li" + }, + { + "name": "DemandHunt", + "url": "https://demandhunt.com" + }, + { + "name": "Designify", + "url": "https://designify.com" + }, + { + "name": "Diff Checker", + "url": "https://diffchecker.com" + }, + { + "name": "DifferenceBetween.info", + "url": "https://differencebetween.info" + }, + { + "name": "Digital Glossary", + "url": "https://digital-glossary.com" + }, + { + "name": "Dimensions", + "url": "https://dimensions.com" + }, + { + "name": "Discoverify Music", + "url": "https://discoverifymusic.com" + }, + { + "name": "discu.eu", + "url": "https://discu.eu" + }, + { + "name": "Do It Yourself", + "url": "https://doityourself.com" + }, + { + "name": "draw.io", + "url": "https://drawio.com" + }, + { + "name": "Drumeo", + "url": "https://drumeo.com" + }, + { + "name": "Dummies", + "url": "https://dummies.com" + }, + { + "name": "Easel.ly", + "url": "https://easel.ly" + }, + { + "name": "Educalingo", + "url": "https://educalingo.com" + }, + { + "name": "Emoji Combos", + "url": "https://emojicombos.com" + }, + { + "name": "EquityBee", + "url": "https://equitybee.com" + }, + { + "name": "EquityZen", + "url": "https://equityzen.com" + }, + { + "name": "Escape Room Tips", + "url": "https://escaperoomtips.com" + }, + { + "name": "Every Noise", + "url": "https://everynoise.com" + }, + { + "name": "Every Time Zone", + "url": "https://everytimezone.com" + }, + { + "name": "Excalideck", + "url": "https://excalideck.com" + }, + { + "name": "Excalidraw", + "url": "https://excalidraw.com" + }, + { + "name": "Extract pics", + "url": "https://extract.pics" + }, + { + "name": "EZGIF", + "url": "https://ezgif.com" + }, + { + "name": "FactSlides", + "url": "https://factslides.com" + }, + { + "name": "FIGR ", + "url": "https://figr.app" + }, + { + "name": "Fine Dictionary", + "url": "https://finedictionary.com" + }, + { + "name": "Fiverr", + "url": "https://fiverr.com" + }, + { + "name": "Fix It Club", + "url": "https://fixitclub.com" + }, + { + "name": "Flightradar24", + "url": "https://flightradar24.com" + }, + { + "name": "FlowCV ", + "url": "https://flowcv.com" + }, + { + "name": "Font Squirrel", + "url": "https://fontsquirrel.com" + }, + { + "name": "FontAwesome", + "url": "https://fontawesome.com" + }, + { + "name": "Fontello ", + "url": "https://fontello.com" + }, + { + "name": "Form to Chatbot", + "url": "https://formtochatbot.com" + }, + { + "name": "Founder Resources", + "url": "https://founderresources.io" + }, + { + "name": "Franz", + "url": "https://meetfranz.com" + }, + { + "name": "Fraze It", + "url": "https://fraze.it" + }, + { + "name": "Freecycle", + "url": "https://freecycle.org" + }, + { + "name": "FreeType", + "url": "https://freetype.org" + }, + { + "name": "FutureM", + "url": "https://futureme.org" + }, + { + "name": "Generated.Photos", + "url": "https://generated.photos" + }, + { + "name": "Get Human", + "url": "https://gethuman.com" + }, + { + "name": "Go Bento", + "url": "https://gobento.com" + }, + { + "name": "Good CV", + "url": "https://goodcv.com" + }, + { + "name": "Grammar Monster", + "url": "https://grammar-monster.com" + }, + { + "name": "Grammar Book", + "url": "https://grammarbook.com" + }, + { + "name": "Gummy Search", + "url": "https://gummysearch.com" + }, + { + "name": "Gumroad", + "url": "https://gumroad.com" + }, + { + "name": "HealthIcons", + "url": "https://healthicons.org" + }, + { + "name": "HexColor", + "url": "https://hexcolor.co" + }, + { + "name": "Hidden Life Radio", + "url": "https://hiddenliferadio.com" + }, + { + "name": "Hired", + "url": "https://lhh.com" + }, + { + "name": "Honey", + "url": "https://joinhoney.com" + }, + { + "name": "HowStuffWorks", + "url": "https://howstuffworks.com" + }, + { + "name": "HugeIcons Pro", + "url": "https://hugeicons.com" + }, + { + "name": "Humble Bundle", + "url": "https://humblebundle.com" + }, + { + "name": "I Have No TV", + "url": "https://ihavenotv.com" + }, + { + "name": "I Miss the Office", + "url": "https://imisstheoffice.eu" + }, + { + "name": "IcoMoon", + "url": "https://icomoon.io" + }, + { + "name": "Iconfinder", + "url": "https://iconfinder.com" + }, + { + "name": "Icon Packs", + "url": "https://iconpacks.net" + }, + { + "name": "Iconshock", + "url": "https://iconshock.com" + }, + { + "name": "Iconz Design", + "url": "https://iconz.design" + }, + { + "name": "iFixit", + "url": "https://ifixit.com" + }, + { + "name": "IFTTT", + "url": "https://ifttt.com" + }, + { + "name": "Illlustrations", + "url": "https://illlustrations.co" + }, + { + "name": "Illustration Kit", + "url": "https://illustrationkit.com" + }, + { + "name": "IMSDB", + "url": "https://imsdb.com" + }, + { + "name": "Incompetech", + "url": "https://incompetech.com" + }, + { + "name": "Incredibox", + "url": "https://incredibox.com" + }, + { + "name": "InnerBod", + "url": "https://innerbody.com" + }, + { + "name": "Instructables", + "url": "https://instructables.com" + }, + { + "name": "Integromat", + "url": "https://make.com" + }, + { + "name": "Investopedia", + "url": "https://investopedia.com" + }, + { + "name": "Japanese Wiki Corpus", + "url": "https://japanesewiki.com" + }, + { + "name": "Jitter.Video", + "url": "https://jitter.video" + }, + { + "name": "Jobspresso", + "url": "https://jobspresso.co" + }, + { + "name": "JPEG-Optimizer", + "url": "https://jpeg-optimizer.com" + }, + { + "name": "JS Remotely", + "url": "https://jsremotely.com" + }, + { + "name": "JScreenFix", + "url": "https://jscreenfix.com" + }, + { + "name": "JSON Resume", + "url": "https://jsonresume.io" + }, + { + "name": "Just Join", + "url": "https://justjoin.it" + }, + { + "name": "Just the Recipe", + "url": "https://justtherecipe.com" + }, + { + "name": "JustRemote", + "url": "https://justremote.co" + }, + { + "name": "JustWatch", + "url": "https://justwatch.com" + }, + { + "name": "Kanopy", + "url": "https://kanopy.com" + }, + { + "name": "Kassellabs", + "url": "https://kassellabs.io" + }, + { + "name": "Key Differences", + "url": "https://keydifferences.com" + }, + { + "name": "Keybase", + "url": "https://keybase.io" + }, + { + "name": "KeyValues", + "url": "https://keyvalues.com" + }, + { + "name": "KHInsider", + "url": "https://khinsider.com" + }, + { + "name": "Killed by Google", + "url": "https://killedbygoogle.com" + }, + { + "name": "Kimovil", + "url": "https://kimovil.com" + }, + { + "name": "Lalal.ai", + "url": "https://www.lalal.ai" + }, + { + "name": "Learn Anything", + "url": "https://learn-anything.xyz" + }, + { + "name": "LendingTree", + "url": "https://lendingtree.com" + }, + { + "name": "Lightyear.fm", + "url": "https://lightyear.fm" + }, + { + "name": "LittleSis", + "url": "https://littlesis.org" + }, + { + "name": "Looria", + "url": "https://looria.com" + }, + { + "name": "Lucidchart", + "url": "https://lucidchart.com" + }, + { + "name": "Lunar", + "url": "https://lunar.fyi" + }, + { + "name": "Manuals Lib", + "url": "https://manualslib.com" + }, + { + "name": "Map Crunch", + "url": "https://mapcrunch.com" + }, + { + "name": "Masterworks", + "url": "https://masterworks.com" + }, + { + "name": "MediaFire", + "url": "https://mediafire.com" + }, + { + "name": "Mixlr", + "url": "https://mixlr.com" + }, + { + "name": "Moises AI", + "url": "https://moises.ai" + }, + { + "name": "Money", + "url": "https://money.com" + }, + { + "name": "Mountain Project", + "url": "https://mountainproject.com" + }, + { + "name": "Movie Map", + "url": "https://movie-map.com" + }, + { + "name": "Movie Sounds", + "url": "https://movie-sounds.org" + }, + { + "name": "MP3Cut", + "url": "https://mp3cut.net" + }, + { + "name": "Murmel", + "url": "https://murmel.social" + }, + { + "name": "Muscle Wiki", + "url": "https://musclewiki.com" + }, + { + "name": "Music-Map", + "url": "https://music-map.com" + }, + { + "name": "MusicTheory.net", + "url": "https://musictheory.net" + }, + { + "name": "MyFonts", + "url": "https://myfonts.com" + }, + { + "name": "MyFridgeFood", + "url": "https://myfridgefood.com" + }, + { + "name": "Nameberry", + "url": "https://nameberry.com" + }, + { + "name": "Namechk", + "url": "https://namechk.com" + }, + { + "name": "Ncase", + "url": "https://ncase.me" + }, + { + "name": "News in Levels", + "url": "https://newsinlevels.com" + }, + { + "name": "Noisli", + "url": "https://noisli.com" + }, + { + "name": "Notes.io", + "url": "https://notes.io" + }, + { + "name": "Novoresume", + "url": "https://novoresume.com" + }, + { + "name": "Ocoya", + "url": "https://ocoya.com" + }, + { + "name": "Old Computers Museum", + "url": "https://oldcomputers.net" + }, + { + "name": "Online Tone Generator", + "url": "https://onlinetonegenerator.com" + }, + { + "name": "Online-Convert", + "url": "https://online-convert.com" + }, + { + "name": "OnlineConversion", + "url": "https://onlineconversion.com" + }, + { + "name": "Online OCR", + "url": "https://onlineocr.net" + }, + { + "name": "OpenWeatherMap", + "url": "https://openweathermap.org" + }, + { + "name": "OrgPad", + "url": "https://orgpad.com" + }, + { + "name": "Passport Index", + "url": "https://passportindex.org" + }, + { + "name": "PDF Candy", + "url": "https://pdfcandy.com" + }, + { + "name": "PDF2DOC", + "url": "https://pdf2doc.com" + }, + { + "name": "PDFescape", + "url": "https://pdfescape.com" + }, + { + "name": "PfpMaker", + "url": "https://pfpmaker.com" + }, + { + "name": "PIDGI Wiki ", + "url": "https://pidgi.net" + }, + { + "name": "PimEyes", + "url": "https://pimeyes.com" + }, + { + "name": "Pipl ", + "url": "https://pipl.com" + }, + { + "name": "PixelBazaar", + "url": "https://pixelbazaar.com" + }, + { + "name": "PixelPaper", + "url": "https://pixelpaper.io" + }, + { + "name": "Ponly", + "url": "https://ponly.com" + }, + { + "name": "PowerToFly", + "url": "https://powertofly.com" + }, + { + "name": "Pretzel Rocks", + "url": "https://pretzel.rocks" + }, + { + "name": "PrintIt", + "url": "https://printit.work" + }, + { + "name": "Prismatext", + "url": "https://prismatext.com" + }, + { + "name": "Puffin Maps", + "url": "https://puffinmaps.com" + }, + { + "name": "Puzzle Loop ", + "url": "https://puzzle-loop.com" + }, + { + "name": "QuoteMaster", + "url": "https://quotemaster.org" + }, + { + "name": "Radio Garden", + "url": "https://radio.garden" + }, + { + "name": "Radiooooo", + "url": "https://radiooooo.com" + }, + { + "name": "Radiosondy", + "url": "https://radiosondy.info" + }, + { + "name": "Rainy Mood", + "url": "https://rainymood.com" + }, + { + "name": "Random Street View", + "url": "https://randomstreetview.com" + }, + { + "name": "Rap4Ever", + "url": "https://rap4all.com" + }, + { + "name": "RareFilm", + "url": "https://rarefilm.net" + }, + { + "name": "Rattibha", + "url": "https://rattibha.com" + }, + { + "name": "Reddit List ", + "url": "https://redditlist.com" + }, + { + "name": "RedditSearch.io", + "url": "https://redditsearch.io" + }, + { + "name": "Reelgood", + "url": "https://reelgood.com" + }, + { + "name": "Reface", + "url": "https://reface.ai" + }, + { + "name": "Rejected.us", + "url": "https://rejected.us" + }, + { + "name": "Relanote", + "url": "https://relanote.com" + }, + { + "name": "Remote Leaf", + "url": "https://remoteleaf.com" + }, + { + "name": "Remote OK", + "url": "https://remoteok.com" + }, + { + "name": "Remote Starter Kit ", + "url": "https://remotestarterkit.com" + }, + { + "name": "Remote.co", + "url": "https://remote.co" + }, + { + "name": "Remote Base ", + "url": "https://remotebase.com" + }, + { + "name": "Remote Bear", + "url": "https://remotebear.io" + }, + { + "name": "Remove.bg", + "url": "https://remove.bg" + }, + { + "name": "Respresso", + "url": "https://respresso.io" + }, + { + "name": "Reveddit", + "url": "https://reveddit.com" + }, + { + "name": "Rhymer", + "url": "https://rhymer.com" + }, + { + "name": "RhymeZone", + "url": "https://rhymezone.com" + }, + { + "name": "Ribbet", + "url": "https://ribbet.com" + }, + { + "name": "Roadmap.sh", + "url": "https://roadmap.sh" + }, + { + "name": "Roadtrippers", + "url": "https://roadtrippers.com" + }, + { + "name": "RxResu.me", + "url": "https://rxresu.me" + }, + { + "name": "SchemeColor", + "url": "https://schemecolor.com" + }, + { + "name": "Screenshot.Guru", + "url": "https://screenshot.guru" + }, + { + "name": "SeatGuru", + "url": "https://seatguru.com" + }, + { + "name": "Sessions", + "url": "https://sessions.us" + }, + { + "name": "Shottr", + "url": "https://shottr.cc" + }, + { + "name": "Signature Maker", + "url": "https://signature-maker.net" + }, + { + "name": "Skip The Drive", + "url": "https://skipthedrive.com" + }, + { + "name": "Slowby", + "url": "https://slowby.travel" + }, + { + "name": "Small World", + "url": "https://smallworld.kiwi" + }, + { + "name": "SmallPDF", + "url": "https://smallpdf.com" + }, + { + "name": "Social Image Maker", + "url": "https://socialimagemaker.io" + }, + { + "name": "Social Sizes", + "url": "https://socialsizes.io" + }, + { + "name": "SoundLove", + "url": "https://soundlove.se" + }, + { + "name": "Spline", + "url": "https://spline.design" + }, + { + "name": "Starkey Comics", + "url": "https://starkeycomics.com" + }, - { - "name": "Statista", - "url": "https://statista.com" - }, - { - "name": "Stolen Camera Finder", - "url": "https://stolencamerafinder.com" - }, - { - "name": "Strobe.Cool", - "url": "https://strobe.cool" - }, - { - "name": "Sumo", - "url": "https://sumo.app" - }, - { - "name": "SuperMeme AI", - "url": "https://supermeme.ai" - }, - { - "name": "Synthesia", - "url": "https://synthesia.io" - }, - { - "name": "TablerIcons", - "url": "https://tablericons.com" - }, - { - "name": "Tango", - "url": "https://tango.us" - }, - { - "name": "TasteDive", - "url": "https://tastedive.com" - }, - { - "name": "TechSpecs", - "url": "https://techspecs.io" - }, - { - "name": "Teoria", - "url": "https://teoria.com" - }, - { - "name": "Text Faces", - "url": "https://textfac.es" - }, - { - "name": "The Balance Money", - "url": "https://thebalancemoney.com" - }, - { - "name": "The Punctuation Guide", - "url": "https://thepunctuationguide.com" - }, - { - "name": "This to That", - "url": "https://thistothat.com" - }, - { - "name": "This vs That", - "url": "https://thisvsthat.io" - }, - { - "name": "ThreadReaderApp ", - "url": "https://threadreaderapp.com" - }, - { - "name": "Thumbly", - "url": "https://tokee.ai" - }, - { - "name": "Tiii.me", - "url": "https://tiii.me" - }, - { - "name": "TikTok Video Downloader", - "url": "https://ttvdl.com" - }, - { - "name": "Time and Date", - "url": "https://timeanddate.com" - }, - { - "name": "Time.is", - "url": "https://time.is" - }, - { - "name": "Title Case", - "url": "https://titlecase.com" - }, - { - "name": "Toaster Central", - "url": "https://toastercentral.com" - }, - { - "name": "Tongue-Twister ", - "url": "https://tongue-twister.net" - }, - { - "name": "TradingView", - "url": "https://tradingview.com" - }, - { - "name": "Transparent Textures", - "url": "https://transparenttextures.com" - }, - { - "name": "Tubi TV", - "url": "https://tubitv.com" - }, - { - "name": "Tunefind", - "url": "https://tunefind.com" - }, - { - "name": "TuneMyMusic", - "url": "https://tunemymusic.com" - }, - { - "name": "Tweepsmap", - "url": "https://fedica.com" - }, - { - "name": "Two Peas and Their Pod", - "url": "https://twopeasandtheirpod.com" - }, - { - "name": "Typatone", - "url": "https://typatone.com" - }, - { - "name": "Under Glass", - "url": "https://underglass.io" - }, - { - "name": "UniCorner", - "url": "https://unicorner.news" - }, - { - "name": "Unita", - "url": "https://unita.co" - }, - { - "name": "UnitConverters", - "url": "https://unitconverters.net" - }, - { - "name": "Unreadit", - "url": "https://unreadit.com" - }, - { - "name": "Unscreen", - "url": "https://unscreen.com" - }, - { - "name": "UnTools ", - "url": "https://untools.co" - }, - { - "name": "Upwork", - "url": "https://upwork.com" - }, - { - "name": "UTF8 Icons", - "url": "https://utf8icons.com" - }, - { - "name": "Vector Magic", - "url": "https://vectormagic.com" - }, - { - "name": "Virtual Vacation", - "url": "https://virtualvacation.us" - }, - { - "name": "Virtual Vocations", - "url": "https://virtualvocations.com" - }, - { - "name": "Visiwig", - "url": "https://visiwig.com" - }, - { - "name": "Visual CV", - "url": "https://visualcv.com" - }, - { - "name": "Vocus.io", - "url": "https://vocus.io" - }, - { - "name": "Voscreen", - "url": "https://voscreen.com" - }, - { - "name": "Wanderprep", - "url": "https://wanderprep.com" - }, - { - "name": "Warmshowers", - "url": "https:/warmshowers.org" - }, - { - "name": "Watch Documentaries", - "url": "https://watchdocumentaries.com" - }, - { - "name": "We Work Remotely", - "url": "https://weworkremotely.com" - }, - { - "name": "Web2PDFConvert", - "url": "https://web2pdfconvert.com" - }, - { - "name": "Welcome to My Garden", - "url": "https://welcometomygarden.org" - }, - { - "name": "When2meet ", - "url": "https://when2meet.com" - }, - { - "name": "Where's George", - "url": "https://wheresgeorge.com" - }, - { - "name": "Where's Willy", - "url": "https://whereswilly.com" - }, - { - "name": "WikiHow", - "url": "https://wikihow.com" - }, - { - "name": "Windy", - "url": "https://www.windy.com" - }, - { - "name": "WonderHowTo", - "url": "https://wonderhowto.com" - }, - { - "name": "Working Nomads", - "url": "https://workingnomads.com" - }, - { - "name": "Wormhole", - "url": "https://wormhole.app" - }, - { - "name": "Y Combinator Jobs", - "url": "https://ycombinator.com" - }, - { - "name": "Yes Promo", - "url": "https://yespromo.me" - }, - { - "name": "YouGlish", - "url": "https://youglish.com" - }, - { - "name": "Zamzar", - "url": "https://zamzar.com" - }, - { - "name": "Zippyshare", - "url": "https://zippyshare.com" - }, - { - "name": "Zoom Earth", - "url": "https://zoom.earth" - }, - { - "name": "Zoom.it", - "url": "https://zoom.it" - } + { + "name": "Statista", + "url": "https://statista.com" + }, + { + "name": "Stolen Camera Finder", + "url": "https://stolencamerafinder.com" + }, + { + "name": "Strobe.Cool", + "url": "https://strobe.cool" + }, + { + "name": "Sumo", + "url": "https://sumo.app" + }, + { + "name": "SuperMeme AI", + "url": "https://supermeme.ai" + }, + { + "name": "Synthesia", + "url": "https://synthesia.io" + }, + { + "name": "TablerIcons", + "url": "https://tablericons.com" + }, + { + "name": "Tango", + "url": "https://tango.us" + }, + { + "name": "TasteDive", + "url": "https://tastedive.com" + }, + { + "name": "TechSpecs", + "url": "https://techspecs.io" + }, + { + "name": "Teoria", + "url": "https://teoria.com" + }, + { + "name": "Text Faces", + "url": "https://textfac.es" + }, + { + "name": "The Balance Money", + "url": "https://thebalancemoney.com" + }, + { + "name": "The Punctuation Guide", + "url": "https://thepunctuationguide.com" + }, + { + "name": "This to That", + "url": "https://thistothat.com" + }, + { + "name": "This vs That", + "url": "https://thisvsthat.io" + }, + { + "name": "ThreadReaderApp ", + "url": "https://threadreaderapp.com" + }, + { + "name": "Thumbly", + "url": "https://tokee.ai" + }, + { + "name": "Tiii.me", + "url": "https://tiii.me" + }, + { + "name": "TikTok Video Downloader", + "url": "https://ttvdl.com" + }, + { + "name": "Time and Date", + "url": "https://timeanddate.com" + }, + { + "name": "Time.is", + "url": "https://time.is" + }, + { + "name": "Title Case", + "url": "https://titlecase.com" + }, + { + "name": "Toaster Central", + "url": "https://toastercentral.com" + }, + { + "name": "Tongue-Twister ", + "url": "https://tongue-twister.net" + }, + { + "name": "TradingView", + "url": "https://tradingview.com" + }, + { + "name": "Transparent Textures", + "url": "https://transparenttextures.com" + }, + { + "name": "Tubi TV", + "url": "https://tubitv.com" + }, + { + "name": "Tunefind", + "url": "https://tunefind.com" + }, + { + "name": "TuneMyMusic", + "url": "https://tunemymusic.com" + }, + { + "name": "Tweepsmap", + "url": "https://fedica.com" + }, + { + "name": "Two Peas and Their Pod", + "url": "https://twopeasandtheirpod.com" + }, + { + "name": "Typatone", + "url": "https://typatone.com" + }, + { + "name": "Under Glass", + "url": "https://underglass.io" + }, + { + "name": "UniCorner", + "url": "https://unicorner.news" + }, + { + "name": "Unita", + "url": "https://unita.co" + }, + { + "name": "UnitConverters", + "url": "https://unitconverters.net" + }, + { + "name": "Unreadit", + "url": "https://unreadit.com" + }, + { + "name": "Unscreen", + "url": "https://unscreen.com" + }, + { + "name": "UnTools ", + "url": "https://untools.co" + }, + { + "name": "Upwork", + "url": "https://upwork.com" + }, + { + "name": "UTF8 Icons", + "url": "https://utf8icons.com" + }, + { + "name": "Vector Magic", + "url": "https://vectormagic.com" + }, + { + "name": "Virtual Vacation", + "url": "https://virtualvacation.us" + }, + { + "name": "Virtual Vocations", + "url": "https://virtualvocations.com" + }, + { + "name": "Visiwig", + "url": "https://visiwig.com" + }, + { + "name": "Visual CV", + "url": "https://visualcv.com" + }, + { + "name": "Vocus.io", + "url": "https://vocus.io" + }, + { + "name": "Voscreen", + "url": "https://voscreen.com" + }, + { + "name": "Wanderprep", + "url": "https://wanderprep.com" + }, + { + "name": "Warmshowers", + "url": "https:/warmshowers.org" + }, + { + "name": "Watch Documentaries", + "url": "https://watchdocumentaries.com" + }, + { + "name": "We Work Remotely", + "url": "https://weworkremotely.com" + }, + { + "name": "Web2PDFConvert", + "url": "https://web2pdfconvert.com" + }, + { + "name": "Welcome to My Garden", + "url": "https://welcometomygarden.org" + }, + { + "name": "When2meet ", + "url": "https://when2meet.com" + }, + { + "name": "Where's George", + "url": "https://wheresgeorge.com" + }, + { + "name": "Where's Willy", + "url": "https://whereswilly.com" + }, + { + "name": "WikiHow", + "url": "https://wikihow.com" + }, + { + "name": "Windy", + "url": "https://www.windy.com" + }, + { + "name": "WonderHowTo", + "url": "https://wonderhowto.com" + }, + { + "name": "Working Nomads", + "url": "https://workingnomads.com" + }, + { + "name": "Wormhole", + "url": "https://wormhole.app" + }, + { + "name": "Y Combinator Jobs", + "url": "https://ycombinator.com" + }, + { + "name": "Yes Promo", + "url": "https://yespromo.me" + }, + { + "name": "YouGlish", + "url": "https://youglish.com" + }, + { + "name": "Zamzar", + "url": "https://zamzar.com" + }, + { + "name": "Zippyshare", + "url": "https://zippyshare.com" + }, + { + "name": "Zoom Earth", + "url": "https://zoom.earth" + }, + { + "name": "Zoom.it", + "url": "https://zoom.it" + } ] diff --git a/Server/utils/imageProcessing.js b/Server/utils/imageProcessing.js index e40d46ff7..470a5c27e 100644 --- a/Server/utils/imageProcessing.js +++ b/Server/utils/imageProcessing.js @@ -4,22 +4,22 @@ import sharp from "sharp"; * @param {} file */ const GenerateAvatarImage = async (file) => { - try { - // Resize to target 64 * 64 - let resizedImageBuffer = await sharp(file.buffer) - .resize({ - width: 64, - height: 64, - fit: "cover", - }) - .toBuffer(); + try { + // Resize to target 64 * 64 + let resizedImageBuffer = await sharp(file.buffer) + .resize({ + width: 64, + height: 64, + fit: "cover", + }) + .toBuffer(); - //Get b64 string - const base64Image = resizedImageBuffer.toString("base64"); - return base64Image; - } catch (error) { - throw error; - } + //Get b64 string + const base64Image = resizedImageBuffer.toString("base64"); + return base64Image; + } catch (error) { + throw error; + } }; export { GenerateAvatarImage }; diff --git a/Server/utils/logger.js b/Server/utils/logger.js index 4d37dc255..6c0bddcd8 100644 --- a/Server/utils/logger.js +++ b/Server/utils/logger.js @@ -12,15 +12,12 @@ import winston from "winston"; * logger.error("User not found!",{"service":"Auth"}) */ const logger = winston.createLogger({ - level: "info", - format: winston.format.combine( - winston.format.timestamp(), - winston.format.json() - ), - transports: [ - new winston.transports.Console(), - new winston.transports.File({ filename: "app.log" }), - ], + level: "info", + format: winston.format.combine(winston.format.timestamp(), winston.format.json()), + transports: [ + new winston.transports.Console(), + new winston.transports.File({ filename: "app.log" }), + ], }); export default logger; diff --git a/Server/utils/messages.js b/Server/utils/messages.js index 159ed5b30..dd7f0f76a 100644 --- a/Server/utils/messages.js +++ b/Server/utils/messages.js @@ -1,125 +1,120 @@ const errorMessages = { - // General Errors: - FRIENDLY_ERROR: "Something went wrong...", - UNKNOWN_ERROR: "An unknown error occurred", + // General Errors: + FRIENDLY_ERROR: "Something went wrong...", + UNKNOWN_ERROR: "An unknown error occurred", - // Auth Controller - UNAUTHORIZED: "Unauthorized access", - AUTH_ADMIN_EXISTS: "Admin already exists", - AUTH_INVITE_NOT_FOUND: "Invite not found", + // Auth Controller + UNAUTHORIZED: "Unauthorized access", + AUTH_ADMIN_EXISTS: "Admin already exists", + AUTH_INVITE_NOT_FOUND: "Invite not found", - //Error handling middleware - UNKNOWN_SERVICE: "Unknown service", - NO_AUTH_TOKEN: "No auth token provided", - INVALID_AUTH_TOKEN: "Invalid auth token", - EXPIRED_AUTH_TOKEN: "Token expired", - NO_REFRESH_TOKEN: "No refresh token provided", - INVALID_REFRESH_TOKEN: "Invalid refresh token", - EXPIRED_REFRESH_TOKEN: "Refresh token expired", - REQUEST_NEW_ACCESS_TOKEN: "Request new access token", + //Error handling middleware + UNKNOWN_SERVICE: "Unknown service", + NO_AUTH_TOKEN: "No auth token provided", + INVALID_AUTH_TOKEN: "Invalid auth token", + EXPIRED_AUTH_TOKEN: "Token expired", + NO_REFRESH_TOKEN: "No refresh token provided", + INVALID_REFRESH_TOKEN: "Invalid refresh token", + EXPIRED_REFRESH_TOKEN: "Refresh token expired", + REQUEST_NEW_ACCESS_TOKEN: "Request new access token", - //Payload - INVALID_PAYLOAD: "Invalid payload", + //Payload + INVALID_PAYLOAD: "Invalid payload", - //Ownership Middleware - VERIFY_OWNER_NOT_FOUND: "Document not found", - VERIFY_OWNER_UNAUTHORIZED: "Unauthorized access", + //Ownership Middleware + VERIFY_OWNER_NOT_FOUND: "Document not found", + VERIFY_OWNER_UNAUTHORIZED: "Unauthorized access", - //Permissions Middleware - INSUFFICIENT_PERMISSIONS: "Insufficient permissions", + //Permissions Middleware + INSUFFICIENT_PERMISSIONS: "Insufficient permissions", - //DB Errors - DB_USER_EXISTS: "User already exists", - DB_USER_NOT_FOUND: "User not found", - DB_TOKEN_NOT_FOUND: "Token not found", - DB_RESET_PASSWORD_BAD_MATCH: - "New password must be different from old password", - DB_FIND_MONITOR_BY_ID: (monitorId) => - `Monitor with id ${monitorId} not found`, - DB_DELETE_CHECKS: (monitorId) => - `No checks found for monitor with id ${monitorId}`, + //DB Errors + DB_USER_EXISTS: "User already exists", + DB_USER_NOT_FOUND: "User not found", + DB_TOKEN_NOT_FOUND: "Token not found", + DB_RESET_PASSWORD_BAD_MATCH: "New password must be different from old password", + DB_FIND_MONITOR_BY_ID: (monitorId) => `Monitor with id ${monitorId} not found`, + DB_DELETE_CHECKS: (monitorId) => `No checks found for monitor with id ${monitorId}`, - //Auth errors - AUTH_INCORRECT_PASSWORD: "Incorrect password", - AUTH_UNAUTHORIZED: "Unauthorized access", + //Auth errors + AUTH_INCORRECT_PASSWORD: "Incorrect password", + AUTH_UNAUTHORIZED: "Unauthorized access", - // Monitor Errors - MONITOR_GET_BY_ID: "Monitor not found", - MONITOR_GET_BY_USER_ID: "No monitors found for user", + // Monitor Errors + MONITOR_GET_BY_ID: "Monitor not found", + MONITOR_GET_BY_USER_ID: "No monitors found for user", - // Job Queue Errors - JOB_QUEUE_WORKER_CLOSE: "Error closing worker", - JOB_QUEUE_DELETE_JOB: "Job not found in queue", - JOB_QUEUE_OBLITERATE: "Error obliterating queue", + // Job Queue Errors + JOB_QUEUE_WORKER_CLOSE: "Error closing worker", + JOB_QUEUE_DELETE_JOB: "Job not found in queue", + JOB_QUEUE_OBLITERATE: "Error obliterating queue", - // PING Operations - PING_CANNOT_RESOLVE: "No response", + // PING Operations + PING_CANNOT_RESOLVE: "No response", }; const successMessages = { - //Alert Controller - ALERT_CREATE: "Alert created successfully", - ALERT_GET_BY_USER: "Got alerts successfully", - ALERT_GET_BY_MONITOR: "Got alerts by Monitor successfully", - ALERT_GET_BY_ID: "Got alert by Id successfully", - ALERT_EDIT: "Alert edited successfully", - ALERT_DELETE: "Alert deleted successfully", + //Alert Controller + ALERT_CREATE: "Alert created successfully", + ALERT_GET_BY_USER: "Got alerts successfully", + ALERT_GET_BY_MONITOR: "Got alerts by Monitor successfully", + ALERT_GET_BY_ID: "Got alert by Id successfully", + ALERT_EDIT: "Alert edited successfully", + ALERT_DELETE: "Alert deleted successfully", - // Auth Controller - AUTH_CREATE_USER: "User created successfully", - AUTH_LOGIN_USER: "User logged in successfully", - AUTH_LOGOUT_USER: "User logged out successfully", - AUTH_UPDATE_USER: "User updated successfully", - AUTH_CREATE_RECOVERY_TOKEN: "Recovery token created successfully", - AUTH_VERIFY_RECOVERY_TOKEN: "Recovery token verified successfully", - AUTH_RESET_PASSWORD: "Password reset successfully", - AUTH_ADMIN_CHECK: "Admin check completed successfully", - AUTH_DELETE_USER: "User deleted successfully", - AUTH_TOKEN_REFRESHED: "Auth token is refreshed", + AUTH_CREATE_USER: "User created successfully", + AUTH_LOGIN_USER: "User logged in successfully", + AUTH_LOGOUT_USER: "User logged out successfully", + AUTH_UPDATE_USER: "User updated successfully", + AUTH_CREATE_RECOVERY_TOKEN: "Recovery token created successfully", + AUTH_VERIFY_RECOVERY_TOKEN: "Recovery token verified successfully", + AUTH_RESET_PASSWORD: "Password reset successfully", + AUTH_ADMIN_CHECK: "Admin check completed successfully", + AUTH_DELETE_USER: "User deleted successfully", + AUTH_TOKEN_REFRESHED: "Auth token is refreshed", - // Check Controller - CHECK_CREATE: "Check created successfully", - CHECK_GET: "Got checks successfully", - CHECK_DELETE: "Checks deleted successfully", - CHECK_UPDATE_TTL: "Checks TTL updated successfully", + // Check Controller + CHECK_CREATE: "Check created successfully", + CHECK_GET: "Got checks successfully", + CHECK_DELETE: "Checks deleted successfully", + CHECK_UPDATE_TTL: "Checks TTL updated successfully", - //Monitor Controller - MONITOR_GET_ALL: "Got all monitors successfully", - MONITOR_STATS_BY_ID: "Got monitor stats by Id successfully", - MONITOR_GET_BY_ID: "Got monitor by Id successfully", - MONITOR_GET_BY_USER_ID: (userId) => `Got monitor for ${userId} successfully"`, - MONITOR_CREATE: "Monitor created successfully", - MONITOR_DELETE: "Monitor deleted successfully", - MONITOR_EDIT: "Monitor edited successfully", - MONITOR_CERTIFICATE: "Got monitor certificate successfully", - MONITOR_DEMO_ADDED: "Successfully added demo monitors", + //Monitor Controller + MONITOR_GET_ALL: "Got all monitors successfully", + MONITOR_STATS_BY_ID: "Got monitor stats by Id successfully", + MONITOR_GET_BY_ID: "Got monitor by Id successfully", + MONITOR_GET_BY_USER_ID: (userId) => `Got monitor for ${userId} successfully"`, + MONITOR_CREATE: "Monitor created successfully", + MONITOR_DELETE: "Monitor deleted successfully", + MONITOR_EDIT: "Monitor edited successfully", + MONITOR_CERTIFICATE: "Got monitor certificate successfully", + MONITOR_DEMO_ADDED: "Successfully added demo monitors", - // Queue Controller - QUEUE_GET_METRICS: "Got metrics successfully", - QUEUE_GET_METRICS: "Got job stats successfully", - QUEUE_ADD_JOB: "Job added successfully", - QUEUE_OBLITERATE: "Queue obliterated", + // Queue Controller + QUEUE_GET_METRICS: "Got metrics successfully", + QUEUE_GET_METRICS: "Got job stats successfully", + QUEUE_ADD_JOB: "Job added successfully", + QUEUE_OBLITERATE: "Queue obliterated", - //Job Queue - JOB_QUEUE_DELETE_JOB: "Job removed successfully", - JOB_QUEUE_OBLITERATE: "Queue OBLITERATED!!!", - JOB_QUEUE_PAUSE_JOB: "Job paused successfully", - JOB_QUEUE_RESUME_JOB: "Job resumed successfully", + //Job Queue + JOB_QUEUE_DELETE_JOB: "Job removed successfully", + JOB_QUEUE_OBLITERATE: "Queue OBLITERATED!!!", + JOB_QUEUE_PAUSE_JOB: "Job paused successfully", + JOB_QUEUE_RESUME_JOB: "Job resumed successfully", - //Maintenance Window Controller - MAINTENANCE_WINDOW_GET_BY_ID: "Got Maintenance Window by Id successfully", - MAINTENANCE_WINDOW_CREATE: "Maintenance Window created successfully", - MAINTENANCE_WINDOW_GET_BY_TEAM: - "Got Maintenance Windows by Team successfully", - MAINTENANCE_WINDOW_DELETE: "Maintenance Window deleted successfully", - MAINTENANCE_WINDOW_EDIT: "Maintenance Window edited successfully", + //Maintenance Window Controller + MAINTENANCE_WINDOW_GET_BY_ID: "Got Maintenance Window by Id successfully", + MAINTENANCE_WINDOW_CREATE: "Maintenance Window created successfully", + MAINTENANCE_WINDOW_GET_BY_TEAM: "Got Maintenance Windows by Team successfully", + MAINTENANCE_WINDOW_DELETE: "Maintenance Window deleted successfully", + MAINTENANCE_WINDOW_EDIT: "Maintenance Window edited successfully", - //Ping Operations - PING_SUCCESS: "Success", + //Ping Operations + PING_SUCCESS: "Success", - // App Settings - GET_APP_SETTINGS: "Got app settings successfully", - UPDATE_APP_SETTINGS: "Updated app settings successfully", + // App Settings + GET_APP_SETTINGS: "Got app settings successfully", + UPDATE_APP_SETTINGS: "Updated app settings successfully", }; export { errorMessages, successMessages }; diff --git a/Server/validation/joi.js b/Server/validation/joi.js index 033650a3d..d1ba107bc 100644 --- a/Server/validation/joi.js +++ b/Server/validation/joi.js @@ -5,13 +5,13 @@ import joi from "joi"; //**************************************** const roleValidatior = (role) => (value, helpers) => { - const hasRole = role.some((role) => value.includes(role)); - if (!hasRole) { - throw new joi.ValidationError( - `You do not have the required authorization. Required roles: ${role.join(", ")}` - ); - } - return value; + const hasRole = role.some((role) => value.includes(role)); + if (!hasRole) { + throw new joi.ValidationError( + `You do not have the required authorization. Required roles: ${role.join(", ")}` + ); + } + return value; }; //**************************************** @@ -19,129 +19,129 @@ const roleValidatior = (role) => (value, helpers) => { //**************************************** const loginValidation = joi.object({ - email: joi - .string() - .email() - .required() - .custom((value, helpers) => { - const lowercasedValue = value.toLowerCase(); - if (value !== lowercasedValue) { - return helpers.message("Email must be in lowercase"); - } - return lowercasedValue; - }), - password: joi - .string() - .min(8) - .required() - .pattern( - /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*()])[A-Za-z0-9!@#$%^&*()]+$/ - ), + email: joi + .string() + .email() + .required() + .custom((value, helpers) => { + const lowercasedValue = value.toLowerCase(); + if (value !== lowercasedValue) { + return helpers.message("Email must be in lowercase"); + } + return lowercasedValue; + }), + password: joi + .string() + .min(8) + .required() + .pattern( + /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*()])[A-Za-z0-9!@#$%^&*()]+$/ + ), }); const registrationBodyValidation = joi.object({ - firstName: joi - .string() - .required() - .pattern(/^[A-Za-z]+$/), - lastName: joi - .string() - .required() - .pattern(/^[A-Za-z]+$/), - email: joi - .string() - .email() - .required() - .custom((value, helpers) => { - const lowercasedValue = value.toLowerCase(); - if (value !== lowercasedValue) { - return helpers.message("Email must be in lowercase"); - } - return lowercasedValue; - }), - password: joi - .string() - .min(8) - .required() - .pattern( - /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*()])[A-Za-z0-9!@#$%^&*()]+$/ - ), - profileImage: joi.any(), - role: joi - .array() - .items(joi.string().valid("superadmin", "admin", "user", "demo")) - .min(1) - .required(), - teamId: joi.string().allow("").required(), - inviteToken: joi.string().allow("").required(), + firstName: joi + .string() + .required() + .pattern(/^[A-Za-z]+$/), + lastName: joi + .string() + .required() + .pattern(/^[A-Za-z]+$/), + email: joi + .string() + .email() + .required() + .custom((value, helpers) => { + const lowercasedValue = value.toLowerCase(); + if (value !== lowercasedValue) { + return helpers.message("Email must be in lowercase"); + } + return lowercasedValue; + }), + password: joi + .string() + .min(8) + .required() + .pattern( + /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*()])[A-Za-z0-9!@#$%^&*()]+$/ + ), + profileImage: joi.any(), + role: joi + .array() + .items(joi.string().valid("superadmin", "admin", "user", "demo")) + .min(1) + .required(), + teamId: joi.string().allow("").required(), + inviteToken: joi.string().allow("").required(), }); const editUserParamValidation = joi.object({ - userId: joi.string().required(), + userId: joi.string().required(), }); const editUserBodyValidation = joi.object({ - firstName: joi.string().pattern(/^[A-Za-z]+$/), - lastName: joi.string().pattern(/^[A-Za-z]+$/), - profileImage: joi.any(), - newPassword: joi - .string() - .min(8) - .pattern( - /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*()])[A-Za-z0-9!@#$%^&*()]+$/ - ), - password: joi - .string() - .min(8) - .pattern( - /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*()])[A-Za-z0-9!@#$%^&*()]+$/ - ), - deleteProfileImage: joi.boolean(), - role: joi.array(), + firstName: joi.string().pattern(/^[A-Za-z]+$/), + lastName: joi.string().pattern(/^[A-Za-z]+$/), + profileImage: joi.any(), + newPassword: joi + .string() + .min(8) + .pattern( + /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*()])[A-Za-z0-9!@#$%^&*()]+$/ + ), + password: joi + .string() + .min(8) + .pattern( + /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*()])[A-Za-z0-9!@#$%^&*()]+$/ + ), + deleteProfileImage: joi.boolean(), + role: joi.array(), }); const recoveryValidation = joi.object({ - email: joi - .string() - .email({ tlds: { allow: false } }) - .required(), + email: joi + .string() + .email({ tlds: { allow: false } }) + .required(), }); const recoveryTokenValidation = joi.object({ - recoveryToken: joi.string().required(), + recoveryToken: joi.string().required(), }); const newPasswordValidation = joi.object({ - recoveryToken: joi.string().required(), - password: joi - .string() - .min(8) - .required() - .pattern( - /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*()])[A-Za-z0-9!@#$%^&*()]+$/ - ), - confirm: joi.string(), + recoveryToken: joi.string().required(), + password: joi + .string() + .min(8) + .required() + .pattern( + /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*()])[A-Za-z0-9!@#$%^&*()]+$/ + ), + confirm: joi.string(), }); const deleteUserParamValidation = joi.object({ - email: joi.string().email().required(), + email: joi.string().email().required(), }); const inviteRoleValidation = joi.object({ - roles: joi.custom(roleValidatior(["admin", "superadmin"])).required(), + roles: joi.custom(roleValidatior(["admin", "superadmin"])).required(), }); const inviteBodyValidation = joi.object({ - email: joi.string().trim().email().required().messages({ - "string.empty": "Email is required", - "string.email": "Must be a valid email address", - }), - role: joi.array().required(), - teamId: joi.string().required(), + email: joi.string().trim().email().required().messages({ + "string.empty": "Email is required", + "string.email": "Must be a valid email address", + }), + role: joi.array().required(), + teamId: joi.string().required(), }); const inviteVerificationBodyValidation = joi.object({ - token: joi.string().required(), + token: joi.string().required(), }); //**************************************** @@ -149,91 +149,95 @@ const inviteVerificationBodyValidation = joi.object({ //**************************************** const getMonitorByIdParamValidation = joi.object({ - monitorId: joi.string().required(), + monitorId: joi.string().required(), }); const getMonitorByIdQueryValidation = joi.object({ - status: joi.boolean(), - sortOrder: joi.string().valid("asc", "desc"), - limit: joi.number(), - dateRange: joi.string().valid("day", "week", "month"), - numToDisplay: joi.number(), - normalize: joi.boolean(), + status: joi.boolean(), + sortOrder: joi.string().valid("asc", "desc"), + limit: joi.number(), + dateRange: joi.string().valid("day", "week", "month"), + numToDisplay: joi.number(), + normalize: joi.boolean(), }); const getMonitorsAndSummaryByTeamIdParamValidation = joi.object({ - teamId: joi.string().required(), + teamId: joi.string().required(), }); const getMonitorsAndSummaryByTeamIdQueryValidation = joi.object({ - type: joi - .alternatives() - .try( - joi.string().valid("http", "ping", "pagespeed"), - joi.array().items(joi.string().valid("http", "ping", "pagespeed")) - ), + type: joi + .alternatives() + .try( + joi.string().valid("http", "ping", "pagespeed"), + joi.array().items(joi.string().valid("http", "ping", "pagespeed")) + ), }); const getMonitorsByTeamIdValidation = joi.object({ - teamId: joi.string().required(), + teamId: joi.string().required(), }); const getMonitorsByTeamIdQueryValidation = joi.object({ - status: joi.boolean(), - checkOrder: joi.string().valid("asc", "desc"), - limit: joi.number(), - normalize: joi.boolean(), - type: joi - .alternatives() - .try( - joi.string().valid("http", "ping", "pagespeed"), - joi.array().items(joi.string().valid("http", "ping", "pagespeed")) - ), - page: joi.number(), - rowsPerPage: joi.number(), - filter: joi.string(), - field: joi.string(), - order: joi.string().valid("asc", "desc"), + status: joi.boolean(), + checkOrder: joi.string().valid("asc", "desc"), + limit: joi.number(), + normalize: joi.boolean(), + type: joi + .alternatives() + .try( + joi.string().valid("http", "ping", "pagespeed"), + joi.array().items(joi.string().valid("http", "ping", "pagespeed")) + ), + page: joi.number(), + rowsPerPage: joi.number(), + filter: joi.string(), + field: joi.string(), + order: joi.string().valid("asc", "desc"), }); const getMonitorStatsByIdParamValidation = joi.object({ - monitorId: joi.string().required(), + monitorId: joi.string().required(), }); const getMonitorStatsByIdQueryValidation = joi.object({ - status: joi.string(), - limit: joi.number(), - sortOrder: joi.string().valid("asc", "desc"), - dateRange: joi.string().valid("day", "week", "month"), - numToDisplay: joi.number(), - normalize: joi.boolean(), + status: joi.string(), + limit: joi.number(), + sortOrder: joi.string().valid("asc", "desc"), + dateRange: joi.string().valid("day", "week", "month"), + numToDisplay: joi.number(), + normalize: joi.boolean(), }); const getCertificateParamValidation = joi.object({ - monitorId: joi.string().required(), + monitorId: joi.string().required(), }); const createMonitorBodyValidation = joi.object({ - _id: joi.string(), - userId: joi.string().required(), - teamId: joi.string().required(), - name: joi.string().required(), - description: joi.string().required(), - type: joi.string().required(), - url: joi.string().required(), - isActive: joi.boolean(), - interval: joi.number(), - notifications: joi.array().items(joi.object()), + _id: joi.string(), + userId: joi.string().required(), + teamId: joi.string().required(), + name: joi.string().required(), + description: joi.string().required(), + type: joi.string().required(), + url: joi.string().required(), + isActive: joi.boolean(), + interval: joi.number(), + notifications: joi.array().items(joi.object()), }); const editMonitorBodyValidation = joi.object({ - name: joi.string(), - description: joi.string(), - interval: joi.number(), - notifications: joi.array().items(joi.object()), + name: joi.string(), + description: joi.string(), + interval: joi.number(), + notifications: joi.array().items(joi.object()), }); const pauseMonitorParamValidation = joi.object({ - monitorId: joi.string().required(), + monitorId: joi.string().required(), +}); + +const getMonitorURLByQueryValidation = joi.object({ + monitorURL: joi.string().uri().required(), }); //**************************************** @@ -241,44 +245,44 @@ const pauseMonitorParamValidation = joi.object({ //**************************************** const createAlertParamValidation = joi.object({ - monitorId: joi.string().required(), + monitorId: joi.string().required(), }); const createAlertBodyValidation = joi.object({ - checkId: joi.string().required(), - monitorId: joi.string().required(), - userId: joi.string().required(), - status: joi.boolean(), - message: joi.string(), - notifiedStatus: joi.boolean(), - acknowledgeStatus: joi.boolean(), + checkId: joi.string().required(), + monitorId: joi.string().required(), + userId: joi.string().required(), + status: joi.boolean(), + message: joi.string(), + notifiedStatus: joi.boolean(), + acknowledgeStatus: joi.boolean(), }); const getAlertsByUserIdParamValidation = joi.object({ - userId: joi.string().required(), + userId: joi.string().required(), }); const getAlertsByMonitorIdParamValidation = joi.object({ - monitorId: joi.string().required(), + monitorId: joi.string().required(), }); const getAlertByIdParamValidation = joi.object({ - alertId: joi.string().required(), + alertId: joi.string().required(), }); const editAlertParamValidation = joi.object({ - alertId: joi.string().required(), + alertId: joi.string().required(), }); const editAlertBodyValidation = joi.object({ - status: joi.boolean(), - message: joi.string(), - notifiedStatus: joi.boolean(), - acknowledgeStatus: joi.boolean(), + status: joi.boolean(), + message: joi.string(), + notifiedStatus: joi.boolean(), + acknowledgeStatus: joi.boolean(), }); const deleteAlertParamValidation = joi.object({ - alertId: joi.string().required(), + alertId: joi.string().required(), }); //**************************************** @@ -286,53 +290,53 @@ const deleteAlertParamValidation = joi.object({ //**************************************** const createCheckParamValidation = joi.object({ - monitorId: joi.string().required(), + monitorId: joi.string().required(), }); const createCheckBodyValidation = joi.object({ - monitorId: joi.string().required(), - status: joi.boolean().required(), - responseTime: joi.number().required(), - statusCode: joi.number().required(), - message: joi.string().required(), + monitorId: joi.string().required(), + status: joi.boolean().required(), + responseTime: joi.number().required(), + statusCode: joi.number().required(), + message: joi.string().required(), }); const getChecksParamValidation = joi.object({ - monitorId: joi.string().required(), + monitorId: joi.string().required(), }); const getChecksQueryValidation = joi.object({ - sortOrder: joi.string().valid("asc", "desc"), - limit: joi.number(), - dateRange: joi.string().valid("day", "week", "month"), - filter: joi.string().valid("all", "down", "resolve"), - page: joi.number(), - rowsPerPage: joi.number(), + sortOrder: joi.string().valid("asc", "desc"), + limit: joi.number(), + dateRange: joi.string().valid("day", "week", "month"), + filter: joi.string().valid("all", "down", "resolve"), + page: joi.number(), + rowsPerPage: joi.number(), }); const getTeamChecksParamValidation = joi.object({ - teamId: joi.string().required(), + teamId: joi.string().required(), }); const getTeamChecksQueryValidation = joi.object({ - sortOrder: joi.string().valid("asc", "desc"), - limit: joi.number(), - dateRange: joi.string().valid("day", "week", "month"), - filter: joi.string().valid("all", "down", "resolve"), - page: joi.number(), - rowsPerPage: joi.number(), + sortOrder: joi.string().valid("asc", "desc"), + limit: joi.number(), + dateRange: joi.string().valid("day", "week", "month"), + filter: joi.string().valid("all", "down", "resolve"), + page: joi.number(), + rowsPerPage: joi.number(), }); const deleteChecksParamValidation = joi.object({ - monitorId: joi.string().required(), + monitorId: joi.string().required(), }); const deleteChecksByTeamIdParamValidation = joi.object({ - teamId: joi.string().required(), + teamId: joi.string().required(), }); const updateChecksTTLBodyValidation = joi.object({ - ttl: joi.number().required(), + ttl: joi.number().required(), }); //**************************************** @@ -340,21 +344,21 @@ const updateChecksTTLBodyValidation = joi.object({ //**************************************** const getPageSpeedCheckParamValidation = joi.object({ - monitorId: joi.string().required(), + monitorId: joi.string().required(), }); //Validation schema for the monitorId parameter const createPageSpeedCheckParamValidation = joi.object({ - monitorId: joi.string().required(), + monitorId: joi.string().required(), }); //Validation schema for the monitorId body const createPageSpeedCheckBodyValidation = joi.object({ - url: joi.string().required(), + url: joi.string().required(), }); const deletePageSpeedCheckParamValidation = joi.object({ - monitorId: joi.string().required(), + monitorId: joi.string().required(), }); //**************************************** @@ -362,120 +366,121 @@ const deletePageSpeedCheckParamValidation = joi.object({ //**************************************** const createMaintenanceWindowBodyValidation = joi.object({ - monitors: joi.array().items(joi.string()).required(), - name: joi.string().required(), - active: joi.boolean(), - start: joi.date().required(), - end: joi.date().required(), - repeat: joi.number().required(), - expiry: joi.date(), + monitors: joi.array().items(joi.string()).required(), + name: joi.string().required(), + active: joi.boolean(), + start: joi.date().required(), + end: joi.date().required(), + repeat: joi.number().required(), + expiry: joi.date(), }); const getMaintenanceWindowByIdParamValidation = joi.object({ - id: joi.string().required(), + id: joi.string().required(), }); const getMaintenanceWindowsByTeamIdQueryValidation = joi.object({ - active: joi.boolean(), - page: joi.number(), - rowsPerPage: joi.number(), - field: joi.string(), - order: joi.string().valid("asc", "desc"), + active: joi.boolean(), + page: joi.number(), + rowsPerPage: joi.number(), + field: joi.string(), + order: joi.string().valid("asc", "desc"), }); const getMaintenanceWindowsByMonitorIdParamValidation = joi.object({ - monitorId: joi.string().required(), + monitorId: joi.string().required(), }); const deleteMaintenanceWindowByIdParamValidation = joi.object({ - id: joi.string().required(), + id: joi.string().required(), }); const editMaintenanceWindowByIdParamValidation = joi.object({ - id: joi.string().required(), + id: joi.string().required(), }); const editMaintenanceByIdWindowBodyValidation = joi.object({ - active: joi.boolean(), - name: joi.string(), - repeat: joi.number(), - start: joi.date(), - end: joi.date(), - expiry: joi.date(), - monitors: joi.array(), + active: joi.boolean(), + name: joi.string(), + repeat: joi.number(), + start: joi.date(), + end: joi.date(), + expiry: joi.date(), + monitors: joi.array(), }); //**************************************** // SettingsValidation //**************************************** const updateAppSettingsBodyValidation = joi.object({ - apiBaseUrl: joi.string().allow(""), - logLevel: joi.string().valid("debug", "none", "error", "warn").allow(""), - clientHost: joi.string().allow(""), - dbType: joi.string().allow(""), - dbConnectionString: joi.string().allow(""), - redisHost: joi.string().allow(""), - redisPort: joi.number().allow(null, ""), - jwtTTL: joi.string().allow(""), - pagespeedApiKey: joi.string().allow(""), - systemEmailHost: joi.string().allow(""), - systemEmailPort: joi.number().allow(""), - systemEmailAddress: joi.string().allow(""), - systemEmailPassword: joi.string().allow(""), + apiBaseUrl: joi.string().allow(""), + logLevel: joi.string().valid("debug", "none", "error", "warn").allow(""), + clientHost: joi.string().allow(""), + dbType: joi.string().allow(""), + dbConnectionString: joi.string().allow(""), + redisHost: joi.string().allow(""), + redisPort: joi.number().allow(null, ""), + jwtTTL: joi.string().allow(""), + pagespeedApiKey: joi.string().allow(""), + systemEmailHost: joi.string().allow(""), + systemEmailPort: joi.number().allow(""), + systemEmailAddress: joi.string().allow(""), + systemEmailPassword: joi.string().allow(""), }); export { - roleValidatior, - loginValidation, - registrationBodyValidation, - recoveryValidation, - recoveryTokenValidation, - newPasswordValidation, - inviteRoleValidation, - inviteBodyValidation, - inviteVerificationBodyValidation, - createMonitorBodyValidation, - getMonitorByIdParamValidation, - getMonitorByIdQueryValidation, - getMonitorsAndSummaryByTeamIdParamValidation, - getMonitorsAndSummaryByTeamIdQueryValidation, - getMonitorsByTeamIdValidation, - getMonitorsByTeamIdQueryValidation, - getMonitorStatsByIdParamValidation, - getMonitorStatsByIdQueryValidation, - getCertificateParamValidation, - editMonitorBodyValidation, - pauseMonitorParamValidation, - editUserParamValidation, - editUserBodyValidation, - createAlertParamValidation, - createAlertBodyValidation, - getAlertsByUserIdParamValidation, - getAlertsByMonitorIdParamValidation, - getAlertByIdParamValidation, - editAlertParamValidation, - editAlertBodyValidation, - deleteAlertParamValidation, - createCheckParamValidation, - createCheckBodyValidation, - getChecksParamValidation, - getChecksQueryValidation, - getTeamChecksParamValidation, - getTeamChecksQueryValidation, - deleteChecksParamValidation, - deleteChecksByTeamIdParamValidation, - updateChecksTTLBodyValidation, - deleteUserParamValidation, - getPageSpeedCheckParamValidation, - createPageSpeedCheckParamValidation, - deletePageSpeedCheckParamValidation, - createPageSpeedCheckBodyValidation, - createMaintenanceWindowBodyValidation, - getMaintenanceWindowByIdParamValidation, - getMaintenanceWindowsByTeamIdQueryValidation, - getMaintenanceWindowsByMonitorIdParamValidation, - deleteMaintenanceWindowByIdParamValidation, - editMaintenanceWindowByIdParamValidation, - editMaintenanceByIdWindowBodyValidation, - updateAppSettingsBodyValidation, + roleValidatior, + loginValidation, + registrationBodyValidation, + recoveryValidation, + recoveryTokenValidation, + newPasswordValidation, + inviteRoleValidation, + inviteBodyValidation, + inviteVerificationBodyValidation, + createMonitorBodyValidation, + getMonitorByIdParamValidation, + getMonitorByIdQueryValidation, + getMonitorsAndSummaryByTeamIdParamValidation, + getMonitorsAndSummaryByTeamIdQueryValidation, + getMonitorsByTeamIdValidation, + getMonitorsByTeamIdQueryValidation, + getMonitorStatsByIdParamValidation, + getMonitorStatsByIdQueryValidation, + getCertificateParamValidation, + editMonitorBodyValidation, + pauseMonitorParamValidation, + getMonitorURLByQueryValidation, + editUserParamValidation, + editUserBodyValidation, + createAlertParamValidation, + createAlertBodyValidation, + getAlertsByUserIdParamValidation, + getAlertsByMonitorIdParamValidation, + getAlertByIdParamValidation, + editAlertParamValidation, + editAlertBodyValidation, + deleteAlertParamValidation, + createCheckParamValidation, + createCheckBodyValidation, + getChecksParamValidation, + getChecksQueryValidation, + getTeamChecksParamValidation, + getTeamChecksQueryValidation, + deleteChecksParamValidation, + deleteChecksByTeamIdParamValidation, + updateChecksTTLBodyValidation, + deleteUserParamValidation, + getPageSpeedCheckParamValidation, + createPageSpeedCheckParamValidation, + deletePageSpeedCheckParamValidation, + createPageSpeedCheckBodyValidation, + createMaintenanceWindowBodyValidation, + getMaintenanceWindowByIdParamValidation, + getMaintenanceWindowsByTeamIdQueryValidation, + getMaintenanceWindowsByMonitorIdParamValidation, + deleteMaintenanceWindowByIdParamValidation, + editMaintenanceWindowByIdParamValidation, + editMaintenanceByIdWindowBodyValidation, + updateAppSettingsBodyValidation, }; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 000000000..f71a78c1f --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "bluewave-uptime", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/renovate.json b/renovate.json new file mode 100644 index 000000000..5db72dd6a --- /dev/null +++ b/renovate.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "config:recommended" + ] +}