From fd12f9eb3ff96662597eeecb6627ae88ac20c8f9 Mon Sep 17 00:00:00 2001 From: Ezri Brimhall Date: Thu, 6 Jun 2024 14:34:41 -0600 Subject: [PATCH 01/96] initial commit --- .eslintrc.cjs | 18 + .gitignore | 24 + .tool-versions | 1 + README.md | 30 + index.html | 13 + package.json | 44 + postcss.config.cjs | 14 + public/vite.svg | 1 + src/App.css | 42 + src/App.tsx | 37 + src/Router.ts | 5 + src/assets/react.svg | 1 + src/index.css | 68 ++ src/interfaces/User.ts | 0 src/main.tsx | 10 + src/pages/Login.tsx | 0 src/vite-env.d.ts | 1 + tsconfig.json | 25 + tsconfig.node.json | 11 + vite.config.ts | 7 + yarn.lock | 1899 ++++++++++++++++++++++++++++++++++++++++ 21 files changed, 2251 insertions(+) create mode 100644 .eslintrc.cjs create mode 100644 .gitignore create mode 100644 .tool-versions create mode 100644 README.md create mode 100644 index.html create mode 100644 package.json create mode 100644 postcss.config.cjs create mode 100644 public/vite.svg create mode 100644 src/App.css create mode 100644 src/App.tsx create mode 100644 src/Router.ts create mode 100644 src/assets/react.svg create mode 100644 src/index.css create mode 100644 src/interfaces/User.ts create mode 100644 src/main.tsx create mode 100644 src/pages/Login.tsx create mode 100644 src/vite-env.d.ts create mode 100644 tsconfig.json create mode 100644 tsconfig.node.json create mode 100644 vite.config.ts create mode 100644 yarn.lock diff --git a/.eslintrc.cjs b/.eslintrc.cjs new file mode 100644 index 0000000..bf8f1cd --- /dev/null +++ b/.eslintrc.cjs @@ -0,0 +1,18 @@ +module.exports = { + root: true, + env: { browser: true, es2020: true }, + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:react-hooks/recommended', + ], + ignorePatterns: ['dist', '.eslintrc.cjs'], + parser: '@typescript-eslint/parser', + plugins: ['react-refresh'], + rules: { + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + }, +}; diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000..02fda06 --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +nodejs 20.14.0 diff --git a/README.md b/README.md new file mode 100644 index 0000000..0d6babe --- /dev/null +++ b/README.md @@ -0,0 +1,30 @@ +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: + +- Configure the top-level `parserOptions` property like this: + +```js +export default { + // other rules... + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + project: ['./tsconfig.json', './tsconfig.node.json'], + tsconfigRootDir: __dirname, + }, +} +``` + +- Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked` +- Optionally add `plugin:@typescript-eslint/stylistic-type-checked` +- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list diff --git a/index.html b/index.html new file mode 100644 index 0000000..e4b78ea --- /dev/null +++ b/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + React + TS + + +
+ + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..41e5eb2 --- /dev/null +++ b/package.json @@ -0,0 +1,44 @@ +{ + "name": "vite-openipam-frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "preview": "vite preview" + }, + "dependencies": { + "@mantine/charts": "^7.10.1", + "@mantine/code-highlight": "^7.10.1", + "@mantine/core": "^7.10.1", + "@mantine/dates": "^7.10.1", + "@mantine/form": "^7.10.1", + "@mantine/hooks": "^7.10.1", + "@mantine/modals": "^7.10.1", + "@mantine/notifications": "^7.10.1", + "@mantine/nprogress": "^7.10.1", + "@mantine/spotlight": "^7.10.1", + "dayjs": "^1.11.11", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-router-dom": "^6.23.1", + "recharts": "2" + }, + "devDependencies": { + "@types/react": "^18.2.66", + "@types/react-dom": "^18.2.22", + "@typescript-eslint/eslint-plugin": "^7.2.0", + "@typescript-eslint/parser": "^7.2.0", + "@vitejs/plugin-react-swc": "^3.5.0", + "eslint": "^8.57.0", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.6", + "postcss": "^8.4.38", + "postcss-preset-mantine": "^1.15.0", + "postcss-simple-vars": "^7.0.1", + "typescript": "^5.2.2", + "vite": "^5.2.0" + } +} diff --git a/postcss.config.cjs b/postcss.config.cjs new file mode 100644 index 0000000..bfba0dd --- /dev/null +++ b/postcss.config.cjs @@ -0,0 +1,14 @@ +module.exports = { + plugins: { + 'postcss-preset-mantine': {}, + 'postcss-simple-vars': { + variables: { + 'mantine-breakpoint-xs': '36em', + 'mantine-breakpoint-sm': '48em', + 'mantine-breakpoint-md': '62em', + 'mantine-breakpoint-lg': '75em', + 'mantine-breakpoint-xl': '88em', + }, + }, + }, +}; diff --git a/public/vite.svg b/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/App.css b/src/App.css new file mode 100644 index 0000000..b9d355d --- /dev/null +++ b/src/App.css @@ -0,0 +1,42 @@ +#root { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; + transition: filter 300ms; +} +.logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); +} +.logo.react:hover { + filter: drop-shadow(0 0 2em #61dafbaa); +} + +@keyframes logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +@media (prefers-reduced-motion: no-preference) { + a:nth-of-type(2) .logo { + animation: logo-spin infinite 20s linear; + } +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..69f1e6c --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,37 @@ +import "@mantine/core/styles.css"; +import { MantineProvider } from "@mantine/core"; +import { useState } from "react"; +import reactLogo from "./assets/react.svg"; +import viteLogo from "/vite.svg"; +import "./App.css"; + +function App() { + const [count, setCount] = useState(0); + + return ( + +
+ + Vite logo + + + React logo + +
+

Vite + React

+
+ +

+ Edit src/App.tsx and save to test HMR +

+
+

+ Click on the Vite and React logos to learn more +

+
+ ); +} + +export default App; diff --git a/src/Router.ts b/src/Router.ts new file mode 100644 index 0000000..2bedf85 --- /dev/null +++ b/src/Router.ts @@ -0,0 +1,5 @@ +import { createBrowserRouter, type RouteObject } from 'react-router-dom'; + +const routes: RouteObject[] = []; + +export const Router = createBrowserRouter(routes); diff --git a/src/assets/react.svg b/src/assets/react.svg new file mode 100644 index 0000000..6c87de9 --- /dev/null +++ b/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/index.css b/src/index.css new file mode 100644 index 0000000..6119ad9 --- /dev/null +++ b/src/index.css @@ -0,0 +1,68 @@ +:root { + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} diff --git a/src/interfaces/User.ts b/src/interfaces/User.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/main.tsx b/src/main.tsx new file mode 100644 index 0000000..3d7150d --- /dev/null +++ b/src/main.tsx @@ -0,0 +1,10 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './App.tsx' +import './index.css' + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + , +) diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx new file mode 100644 index 0000000..e69de29 diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..a7fc6fb --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 0000000..97ede7e --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "strict": true + }, + "include": ["vite.config.ts"] +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..861b04b --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react-swc' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], +}) diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..89f83ca --- /dev/null +++ b/yarn.lock @@ -0,0 +1,1899 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/runtime@^7.20.13", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.7.tgz#f4f0d5530e8dbdf59b3451b9b3e594b6ba082e12" + integrity sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw== + dependencies: + regenerator-runtime "^0.14.0" + +"@esbuild/aix-ppc64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz#a70f4ac11c6a1dfc18b8bbb13284155d933b9537" + integrity sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g== + +"@esbuild/android-arm64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz#db1c9202a5bc92ea04c7b6840f1bbe09ebf9e6b9" + integrity sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg== + +"@esbuild/android-arm@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.20.2.tgz#3b488c49aee9d491c2c8f98a909b785870d6e995" + integrity sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w== + +"@esbuild/android-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.20.2.tgz#3b1628029e5576249d2b2d766696e50768449f98" + integrity sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg== + +"@esbuild/darwin-arm64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz#6e8517a045ddd86ae30c6608c8475ebc0c4000bb" + integrity sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA== + +"@esbuild/darwin-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz#90ed098e1f9dd8a9381695b207e1cff45540a0d0" + integrity sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA== + +"@esbuild/freebsd-arm64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz#d71502d1ee89a1130327e890364666c760a2a911" + integrity sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw== + +"@esbuild/freebsd-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz#aa5ea58d9c1dd9af688b8b6f63ef0d3d60cea53c" + integrity sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw== + +"@esbuild/linux-arm64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz#055b63725df678379b0f6db9d0fa85463755b2e5" + integrity sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A== + +"@esbuild/linux-arm@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz#76b3b98cb1f87936fbc37f073efabad49dcd889c" + integrity sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg== + +"@esbuild/linux-ia32@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz#c0e5e787c285264e5dfc7a79f04b8b4eefdad7fa" + integrity sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig== + +"@esbuild/linux-loong64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz#a6184e62bd7cdc63e0c0448b83801001653219c5" + integrity sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ== + +"@esbuild/linux-mips64el@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz#d08e39ce86f45ef8fc88549d29c62b8acf5649aa" + integrity sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA== + +"@esbuild/linux-ppc64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz#8d252f0b7756ffd6d1cbde5ea67ff8fd20437f20" + integrity sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg== + +"@esbuild/linux-riscv64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz#19f6dcdb14409dae607f66ca1181dd4e9db81300" + integrity sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg== + +"@esbuild/linux-s390x@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz#3c830c90f1a5d7dd1473d5595ea4ebb920988685" + integrity sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ== + +"@esbuild/linux-x64@0.20.2": + version "0.20.2" + resolved "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz" + integrity sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw== + +"@esbuild/netbsd-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz#e771c8eb0e0f6e1877ffd4220036b98aed5915e6" + integrity sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ== + +"@esbuild/openbsd-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz#9a795ae4b4e37e674f0f4d716f3e226dd7c39baf" + integrity sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ== + +"@esbuild/sunos-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz#7df23b61a497b8ac189def6e25a95673caedb03f" + integrity sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w== + +"@esbuild/win32-arm64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz#f1ae5abf9ca052ae11c1bc806fb4c0f519bacf90" + integrity sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ== + +"@esbuild/win32-ia32@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz#241fe62c34d8e8461cd708277813e1d0ba55ce23" + integrity sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ== + +"@esbuild/win32-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz#9c907b21e30a52db959ba4f80bb01a0cc403d5cc" + integrity sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ== + +"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": + version "4.4.0" + resolved "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz" + integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== + dependencies: + eslint-visitor-keys "^3.3.0" + +"@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.6.1": + version "4.10.1" + resolved "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.1.tgz" + integrity sha512-Zm2NGpWELsQAD1xsJzGQpYfvICSsFkEpU0jxBjfdC6uNEWXcHnfs9hScFWtXVDVl+rBQJGrl4g1vcKIejpH9dA== + +"@eslint/eslintrc@^2.1.4": + version "2.1.4" + resolved "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz" + integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ== + 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" + +"@eslint/js@8.57.0": + version "8.57.0" + resolved "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz" + integrity sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g== + +"@floating-ui/core@^1.0.0": + version "1.6.2" + resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.6.2.tgz#d37f3e0ac1f1c756c7de45db13303a266226851a" + integrity sha512-+2XpQV9LLZeanU4ZevzRnGFg2neDeKHgFLjP6YLW+tly0IvrhqT4u8enLGjLH3qeh85g19xY5rsAusfwTdn5lg== + dependencies: + "@floating-ui/utils" "^0.2.0" + +"@floating-ui/dom@^1.0.0": + version "1.6.5" + resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.5.tgz#323f065c003f1d3ecf0ff16d2c2c4d38979f4cb9" + integrity sha512-Nsdud2X65Dz+1RHjAIP0t8z5e2ff/IRbei6BqFrl1urT8sDVzM1HMQ+R0XcU5ceRfyO3I6ayeqIfh+6Wb8LGTw== + dependencies: + "@floating-ui/core" "^1.0.0" + "@floating-ui/utils" "^0.2.0" + +"@floating-ui/react-dom@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.1.0.tgz#4f0e5e9920137874b2405f7d6c862873baf4beff" + integrity sha512-lNzj5EQmEKn5FFKc04+zasr09h/uX8RtJRNj5gUXsSQIXHVWTVh+hVAg1vOMCexkX8EgvemMvIFpQfkosnVNyA== + dependencies: + "@floating-ui/dom" "^1.0.0" + +"@floating-ui/react@^0.26.9": + version "0.26.16" + resolved "https://registry.yarnpkg.com/@floating-ui/react/-/react-0.26.16.tgz#3415a087f452165161c2d313d1d57e8142894679" + integrity sha512-HEf43zxZNAI/E781QIVpYSF3K2VH4TTYZpqecjdsFkjsaU1EbaWcM++kw0HXFffj7gDUcBFevX8s0rQGQpxkow== + dependencies: + "@floating-ui/react-dom" "^2.1.0" + "@floating-ui/utils" "^0.2.0" + tabbable "^6.0.0" + +"@floating-ui/utils@^0.2.0": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.2.tgz#d8bae93ac8b815b2bd7a98078cf91e2724ef11e5" + integrity sha512-J4yDIIthosAsRZ5CPYP/jQvUAQtlZTTD/4suA08/FEnlxqW3sKS9iAhgsa9VYLZ6vDHn/ixJgIqRQPotoBjxIw== + +"@humanwhocodes/config-array@^0.11.14": + version "0.11.14" + resolved "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz" + integrity sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg== + dependencies: + "@humanwhocodes/object-schema" "^2.0.2" + debug "^4.3.1" + minimatch "^3.0.5" + +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + +"@humanwhocodes/object-schema@^2.0.2": + 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== + +"@mantine/charts@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@mantine/charts/-/charts-7.10.1.tgz#c1b92ccac16d05b87cc103adcd79f0e0c2f87c62" + integrity sha512-fMy2EmgegdHVkrtnRO8ync8kqOJoXqixxc1JDmhn9tks4lSvKAKB9ui8OyaTUOWzls/Cs0VfyW51sB/HKe5faw== + +"@mantine/code-highlight@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@mantine/code-highlight/-/code-highlight-7.10.1.tgz#96f90c5c142d00be64c4177dab9ae6803def187f" + integrity sha512-ZeqBnd/i6CNF8avmjgYNqo9hKFnrzYoKV13OrKAHNRZk7vdbBGoSeVYF1vq+ChDBOZQfLOW+2naTi3VdzwLOhg== + dependencies: + clsx "^2.1.1" + highlight.js "^11.9.0" + +"@mantine/core@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@mantine/core/-/core-7.10.1.tgz#c90a2aed1e47ece38f745db11c1cff56fd5719b9" + integrity sha512-l9ypojKN3PjwO1CSLIsqxi7mA25+7w+xc71Q+JuCCREI0tuGwkZsKbIOpuTATIJOjPh8ycLiW7QxX1LYsRTq6w== + dependencies: + "@floating-ui/react" "^0.26.9" + clsx "^2.1.1" + react-number-format "^5.3.1" + react-remove-scroll "^2.5.7" + react-textarea-autosize "8.5.3" + type-fest "^4.12.0" + +"@mantine/dates@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@mantine/dates/-/dates-7.10.1.tgz#2cbc0a69e713b4046a83662cfdbc40784c5bbaa4" + integrity sha512-XkzYaHgzJPrquG78/exJd0dLeghJvRgypfSRwFH7IcR34Eo2MD3nsuorZp09i9sYnROcXqIFhZWgwk5cqg/8nw== + dependencies: + clsx "^2.1.1" + +"@mantine/form@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@mantine/form/-/form-7.10.1.tgz#86b867454b7490f4ca58c924d85d4170520cec4d" + integrity sha512-mZwzg4GEWKEDKEIZu9FmSpGFzYYhFD2YArVOXUM0MMciUqX7yxSCon1PaPJxrV8ldc6FE+JLVI2+G2KVxJ3ZXA== + dependencies: + fast-deep-equal "^3.1.3" + klona "^2.0.6" + +"@mantine/hooks@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@mantine/hooks/-/hooks-7.10.1.tgz#68d12570f0dad127555904973ec78ae40065ae31" + integrity sha512-0EH9WBWUdtQLGU3Ak+csQ77EtUxI6pPNfwZdRJQWcaA3f8SFOLo9h9CGxiikFExerhvuCeUlaTf3s+TB9Op/rw== + +"@mantine/modals@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@mantine/modals/-/modals-7.10.1.tgz#28cab8bdf432ac185b33fb3bcaeae93bc4e85747" + integrity sha512-2riQSNpVV7f0baizlqcggz9hx9/+y6SQTnW3zEkl/RIkuyK9dpeMFUG6M+M8ntwP79b7x9n7Em9PMWxRbgi28A== + +"@mantine/notifications@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@mantine/notifications/-/notifications-7.10.1.tgz#05622ec6966a8eaa77cf4e5c65ec9cf2ad0f5bcd" + integrity sha512-cx3JR3BJzEzH6t2EF1ysrWVY/rdJk0WbSBQo/qFamJd2sbU+8XAHriI8Cx6hNo7uRGCwd8VGAj7Cf3aWK2VC5A== + dependencies: + "@mantine/store" "7.10.1" + react-transition-group "4.4.5" + +"@mantine/nprogress@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@mantine/nprogress/-/nprogress-7.10.1.tgz#d79a2ca73f6d7d6aa01423fdf8a3e16c87385ea9" + integrity sha512-irzVf35N8YQJEM5TwlzF0lZeguHeMejN005IMFg2UvlMw8ba+WiXGlK4vELhCq0+37yDqRsxl8NKf6crQiNVRQ== + dependencies: + "@mantine/store" "7.10.1" + +"@mantine/spotlight@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@mantine/spotlight/-/spotlight-7.10.1.tgz#469ce55bd9b5501083d0901d93aaec95fa4004b6" + integrity sha512-0G9HLaSbgJvSZRFIsEPyLUPM9heLBsYsKNYAeh6gTE6Q0/rdc5WrXxPUyOzdUaZVu4JVPvdzx/Ov5fa+kGgU4g== + dependencies: + "@mantine/store" "7.10.1" + +"@mantine/store@7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@mantine/store/-/store-7.10.1.tgz#b8201cb0b76bd3feaeecc3818155f06d967e46f4" + integrity sha512-KrGBsSoMsfrYeLxPwf5rFv0s2Nl/4wf+AaF/U1SpQrMgPI8vYokPXx52Wp3jCmlo12NCZnCIG+/6YHAdTWH1qQ== + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": + version "1.2.8" + resolved "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@remix-run/router@1.16.1": + version "1.16.1" + resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.16.1.tgz#73db3c48b975eeb06d0006481bde4f5f2d17d1cd" + integrity sha512-es2g3dq6Nb07iFxGk5GuHN20RwBZOsuDQN7izWIisUcv9r+d2C5jQxqmgkdebXgReWfiyUabcki6Fg77mSNrig== + +"@rollup/rollup-android-arm-eabi@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.0.tgz#bbd0e616b2078cd2d68afc9824d1fadb2f2ffd27" + integrity sha512-Tya6xypR10giZV1XzxmH5wr25VcZSncG0pZIjfePT0OVBvqNEurzValetGNarVrGiq66EBVAFn15iYX4w6FKgQ== + +"@rollup/rollup-android-arm64@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.18.0.tgz#97255ef6384c5f73f4800c0de91f5f6518e21203" + integrity sha512-avCea0RAP03lTsDhEyfy+hpfr85KfyTctMADqHVhLAF3MlIkq83CP8UfAHUssgXTYd+6er6PaAhx/QGv4L1EiA== + +"@rollup/rollup-darwin-arm64@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.18.0.tgz#b6dd74e117510dfe94541646067b0545b42ff096" + integrity sha512-IWfdwU7KDSm07Ty0PuA/W2JYoZ4iTj3TUQjkVsO/6U+4I1jN5lcR71ZEvRh52sDOERdnNhhHU57UITXz5jC1/w== + +"@rollup/rollup-darwin-x64@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.18.0.tgz#e07d76de1cec987673e7f3d48ccb8e106d42c05c" + integrity sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA== + +"@rollup/rollup-linux-arm-gnueabihf@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.18.0.tgz#9f1a6d218b560c9d75185af4b8bb42f9f24736b8" + integrity sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA== + +"@rollup/rollup-linux-arm-musleabihf@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.18.0.tgz#53618b92e6ffb642c7b620e6e528446511330549" + integrity sha512-l3m9ewPgjQSXrUMHg93vt0hYCGnrMOcUpTz6FLtbwljo2HluS4zTXFy2571YQbisTnfTKPZ01u/ukJdQTLGh9A== + +"@rollup/rollup-linux-arm64-gnu@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.18.0.tgz#99a7ba5e719d4f053761a698f7b52291cefba577" + integrity sha512-rJ5D47d8WD7J+7STKdCUAgmQk49xuFrRi9pZkWoRD1UeSMakbcepWXPF8ycChBoAqs1pb2wzvbY6Q33WmN2ftw== + +"@rollup/rollup-linux-arm64-musl@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.18.0.tgz#f53db99a45d9bc00ce94db8a35efa7c3c144a58c" + integrity sha512-be6Yx37b24ZwxQ+wOQXXLZqpq4jTckJhtGlWGZs68TgdKXJgw54lUUoFYrg6Zs/kjzAQwEwYbp8JxZVzZLRepQ== + +"@rollup/rollup-linux-powerpc64le-gnu@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.18.0.tgz#cbb0837408fe081ce3435cf3730e090febafc9bf" + integrity sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA== + +"@rollup/rollup-linux-riscv64-gnu@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.18.0.tgz#8ed09c1d1262ada4c38d791a28ae0fea28b80cc9" + integrity sha512-ROCM7i+m1NfdrsmvwSzoxp9HFtmKGHEqu5NNDiZWQtXLA8S5HBCkVvKAxJ8U+CVctHwV2Gb5VUaK7UAkzhDjlg== + +"@rollup/rollup-linux-s390x-gnu@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.18.0.tgz#938138d3c8e0c96f022252a28441dcfb17afd7ec" + integrity sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg== + +"@rollup/rollup-linux-x64-gnu@4.18.0": + version "4.18.0" + resolved "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.18.0.tgz" + integrity sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w== + +"@rollup/rollup-linux-x64-musl@4.18.0": + version "4.18.0" + resolved "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.18.0.tgz" + integrity sha512-LKaqQL9osY/ir2geuLVvRRs+utWUNilzdE90TpyoX0eNqPzWjRm14oMEE+YLve4k/NAqCdPkGYDaDF5Sw+xBfg== + +"@rollup/rollup-win32-arm64-msvc@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.18.0.tgz#ed6603e93636a96203c6915be4117245c1bd2daf" + integrity sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA== + +"@rollup/rollup-win32-ia32-msvc@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.18.0.tgz#14e0b404b1c25ebe6157a15edb9c46959ba74c54" + integrity sha512-Txjh+IxBPbkUB9+SXZMpv+b/vnTEtFyfWZgJ6iyCmt2tdx0OF5WhFowLmnh8ENGNpfUlUZkdI//4IEmhwPieNg== + +"@rollup/rollup-win32-x64-msvc@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.0.tgz#5d694d345ce36b6ecf657349e03eb87297e68da4" + integrity sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g== + +"@swc/core-darwin-arm64@1.5.25": + version "1.5.25" + resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.5.25.tgz#fca6bb56ae3b6bcb33a12acf49caa58a37e02769" + integrity sha512-YbD0SBgVJS2DM0vwJTU5m7+wOyCjHPBDMf3nCBJQzFZzOLzK11eRW7SzU2jhJHr9HI9sKcNFfN4lIC2Sj+4inA== + +"@swc/core-darwin-x64@1.5.25": + version "1.5.25" + resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.5.25.tgz#6f8764df464b27623f4d0feb3f4a9dfaceb6e2d9" + integrity sha512-OhP4TROT6gQuozn+ah0Y4UidSdgDmxwtQq3lgCUIAxJYErJAQ82/Y0kve2UaNmkSGjOHU+/b4siHPrYTkXOk0Q== + +"@swc/core-linux-arm-gnueabihf@1.5.25": + version "1.5.25" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.5.25.tgz#8056be84c2db35366a8980b03137de506e5ec0c7" + integrity sha512-tNmUfrAHxN2gvYPyYNnHx2CYlPO7DGAUuK/bZrqawu++djcg+atAV3eI3XYJgmHId7/sYAlDQ9wjkrOLofFjVg== + +"@swc/core-linux-arm64-gnu@1.5.25": + version "1.5.25" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.5.25.tgz#2430eb0d385b396ae6a8a9ea98993f2026ffa08a" + integrity sha512-stzpke+bRaNFM/HrZPRjX0aQZ86S/2DChVCwb8NAV1n5lu9mz1CS750y7WbbtX/KZjk92FsCeRy2qwkvjI0gWw== + +"@swc/core-linux-arm64-musl@1.5.25": + version "1.5.25" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.5.25.tgz#a41706f1c81956bfe2359fed7d2b8358cab37555" + integrity sha512-UckUfDYedish/bj2V1jgQDGgouLhyRpG7jgF3mp8jHir11V2K6JiTyjFoz99eOiclS3+hNdr4QLJ+ifrQMJNZw== + +"@swc/core-linux-x64-gnu@1.5.25": + version "1.5.25" + resolved "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.5.25.tgz" + integrity sha512-LwbJEgNT3lXbvz4WFzVNXNvs8DvxpoXjMZk9K9Hig8tmZQJKHC2qZTGomcyK5EFzfj2HBuBXZnAEW8ZT9PcEaA== + +"@swc/core-linux-x64-musl@1.5.25": + version "1.5.25" + resolved "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.5.25.tgz" + integrity sha512-rsepMTgml0EkswWkBpg3Wrjj5eqjwTzZN5omAn1klzXSZnClTrfeHvBuoIJYVr1yx+jmBkqySgME2p7+magUAw== + +"@swc/core-win32-arm64-msvc@1.5.25": + version "1.5.25" + resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.5.25.tgz#a055e059d320fe100fb89556f172a8f05ec55589" + integrity sha512-DJDsLBsRBV3uQBShRK2x6fqzABp9RLNVxDUpTTvUjc7qywJ8vS/yn+POK/zCyVEqLagf1z/8D5CEQ+RAIJq1NA== + +"@swc/core-win32-ia32-msvc@1.5.25": + version "1.5.25" + resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.5.25.tgz#820f04b2ef12393dc5c1f18189bb63a6673878b8" + integrity sha512-BARL1ulHol53MEKC1ZVWM3A3FP757UUgG5Q8v97za+4a1SaIgbwvAQyHDxMYWi9+ij+OapK8YnWjJcFa17g8dw== + +"@swc/core-win32-x64-msvc@1.5.25": + version "1.5.25" + resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.5.25.tgz#27883b25867828d88f9289281679e0aff6f5316a" + integrity sha512-o+MHUWrQI9iR6EusEV8eNU2Ezi3KtlhUR4gfptQN5MbVzlgjTvQbhiKpE1GYOxp+0BLBbKRwITKOcdhxfEJ2Uw== + +"@swc/core@^1.5.7": + version "1.5.25" + resolved "https://registry.npmjs.org/@swc/core/-/core-1.5.25.tgz" + integrity sha512-qdGEIdLVoTjEQ7w72UyyQ0wLFY4XbHfZiidmPHKJQsvSXzdpHXxPdlTCea/mY4AhMqo/M+pvkJSXJAxZnFl7qw== + dependencies: + "@swc/counter" "^0.1.3" + "@swc/types" "^0.1.7" + optionalDependencies: + "@swc/core-darwin-arm64" "1.5.25" + "@swc/core-darwin-x64" "1.5.25" + "@swc/core-linux-arm-gnueabihf" "1.5.25" + "@swc/core-linux-arm64-gnu" "1.5.25" + "@swc/core-linux-arm64-musl" "1.5.25" + "@swc/core-linux-x64-gnu" "1.5.25" + "@swc/core-linux-x64-musl" "1.5.25" + "@swc/core-win32-arm64-msvc" "1.5.25" + "@swc/core-win32-ia32-msvc" "1.5.25" + "@swc/core-win32-x64-msvc" "1.5.25" + +"@swc/counter@^0.1.3": + version "0.1.3" + resolved "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz" + integrity sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ== + +"@swc/types@^0.1.7": + version "0.1.7" + resolved "https://registry.npmjs.org/@swc/types/-/types-0.1.7.tgz" + integrity sha512-scHWahbHF0eyj3JsxG9CFJgFdFNaVQCNAimBlT6PzS3n/HptxqREjsm4OH6AN3lYcffZYSPxXW8ua2BEHp0lJQ== + dependencies: + "@swc/counter" "^0.1.3" + +"@types/d3-array@^3.0.3": + version "3.2.1" + resolved "https://registry.yarnpkg.com/@types/d3-array/-/d3-array-3.2.1.tgz#1f6658e3d2006c4fceac53fde464166859f8b8c5" + integrity sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg== + +"@types/d3-color@*": + version "3.1.3" + resolved "https://registry.yarnpkg.com/@types/d3-color/-/d3-color-3.1.3.tgz#368c961a18de721da8200e80bf3943fb53136af2" + integrity sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A== + +"@types/d3-ease@^3.0.0": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@types/d3-ease/-/d3-ease-3.0.2.tgz#e28db1bfbfa617076f7770dd1d9a48eaa3b6c51b" + integrity sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA== + +"@types/d3-interpolate@^3.0.1": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz#412b90e84870285f2ff8a846c6eb60344f12a41c" + integrity sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA== + dependencies: + "@types/d3-color" "*" + +"@types/d3-path@*": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@types/d3-path/-/d3-path-3.1.0.tgz#2b907adce762a78e98828f0b438eaca339ae410a" + integrity sha512-P2dlU/q51fkOc/Gfl3Ul9kicV7l+ra934qBFXCFhrZMOL6du1TM0pm1ThYvENukyOn5h9v+yMJ9Fn5JK4QozrQ== + +"@types/d3-scale@^4.0.2": + version "4.0.8" + resolved "https://registry.yarnpkg.com/@types/d3-scale/-/d3-scale-4.0.8.tgz#d409b5f9dcf63074464bf8ddfb8ee5a1f95945bb" + integrity sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ== + dependencies: + "@types/d3-time" "*" + +"@types/d3-shape@^3.1.0": + version "3.1.6" + resolved "https://registry.yarnpkg.com/@types/d3-shape/-/d3-shape-3.1.6.tgz#65d40d5a548f0a023821773e39012805e6e31a72" + integrity sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA== + dependencies: + "@types/d3-path" "*" + +"@types/d3-time@*", "@types/d3-time@^3.0.0": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/d3-time/-/d3-time-3.0.3.tgz#3c186bbd9d12b9d84253b6be6487ca56b54f88be" + integrity sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw== + +"@types/d3-timer@^3.0.0": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@types/d3-timer/-/d3-timer-3.0.2.tgz#70bbda77dc23aa727413e22e214afa3f0e852f70" + integrity sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw== + +"@types/estree@1.0.5": + version "1.0.5" + resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz" + integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== + +"@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== + +"@types/react-dom@^18.2.22": + version "18.3.0" + resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz" + integrity sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg== + dependencies: + "@types/react" "*" + +"@types/react@*", "@types/react@^18.2.66": + 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== + dependencies: + "@types/prop-types" "*" + csstype "^3.0.2" + +"@typescript-eslint/eslint-plugin@^7.2.0": + version "7.12.0" + resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.12.0.tgz" + integrity sha512-7F91fcbuDf/d3S8o21+r3ZncGIke/+eWk0EpO21LXhDfLahriZF9CGj4fbAetEjlaBdjdSm9a6VeXbpbT6Z40Q== + dependencies: + "@eslint-community/regexpp" "^4.10.0" + "@typescript-eslint/scope-manager" "7.12.0" + "@typescript-eslint/type-utils" "7.12.0" + "@typescript-eslint/utils" "7.12.0" + "@typescript-eslint/visitor-keys" "7.12.0" + graphemer "^1.4.0" + ignore "^5.3.1" + natural-compare "^1.4.0" + ts-api-utils "^1.3.0" + +"@typescript-eslint/parser@^7.2.0": + version "7.12.0" + resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.12.0.tgz" + integrity sha512-dm/J2UDY3oV3TKius2OUZIFHsomQmpHtsV0FTh1WO8EKgHLQ1QCADUqscPgTpU+ih1e21FQSRjXckHn3txn6kQ== + dependencies: + "@typescript-eslint/scope-manager" "7.12.0" + "@typescript-eslint/types" "7.12.0" + "@typescript-eslint/typescript-estree" "7.12.0" + "@typescript-eslint/visitor-keys" "7.12.0" + debug "^4.3.4" + +"@typescript-eslint/scope-manager@7.12.0": + version "7.12.0" + resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.12.0.tgz" + integrity sha512-itF1pTnN6F3unPak+kutH9raIkL3lhH1YRPGgt7QQOh43DQKVJXmWkpb+vpc/TiDHs6RSd9CTbDsc/Y+Ygq7kg== + dependencies: + "@typescript-eslint/types" "7.12.0" + "@typescript-eslint/visitor-keys" "7.12.0" + +"@typescript-eslint/type-utils@7.12.0": + version "7.12.0" + resolved "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.12.0.tgz" + integrity sha512-lib96tyRtMhLxwauDWUp/uW3FMhLA6D0rJ8T7HmH7x23Gk1Gwwu8UZ94NMXBvOELn6flSPiBrCKlehkiXyaqwA== + dependencies: + "@typescript-eslint/typescript-estree" "7.12.0" + "@typescript-eslint/utils" "7.12.0" + debug "^4.3.4" + ts-api-utils "^1.3.0" + +"@typescript-eslint/types@7.12.0": + version "7.12.0" + resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.12.0.tgz" + integrity sha512-o+0Te6eWp2ppKY3mLCU+YA9pVJxhUJE15FV7kxuD9jgwIAa+w/ycGJBMrYDTpVGUM/tgpa9SeMOugSabWFq7bg== + +"@typescript-eslint/typescript-estree@7.12.0": + version "7.12.0" + resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.12.0.tgz" + integrity sha512-5bwqLsWBULv1h6pn7cMW5dXX/Y2amRqLaKqsASVwbBHMZSnHqE/HN4vT4fE0aFsiwxYvr98kqOWh1a8ZKXalCQ== + dependencies: + "@typescript-eslint/types" "7.12.0" + "@typescript-eslint/visitor-keys" "7.12.0" + debug "^4.3.4" + globby "^11.1.0" + is-glob "^4.0.3" + minimatch "^9.0.4" + semver "^7.6.0" + ts-api-utils "^1.3.0" + +"@typescript-eslint/utils@7.12.0": + version "7.12.0" + resolved "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.12.0.tgz" + integrity sha512-Y6hhwxwDx41HNpjuYswYp6gDbkiZ8Hin9Bf5aJQn1bpTs3afYY4GX+MPYxma8jtoIV2GRwTM/UJm/2uGCVv+DQ== + dependencies: + "@eslint-community/eslint-utils" "^4.4.0" + "@typescript-eslint/scope-manager" "7.12.0" + "@typescript-eslint/types" "7.12.0" + "@typescript-eslint/typescript-estree" "7.12.0" + +"@typescript-eslint/visitor-keys@7.12.0": + version "7.12.0" + resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.12.0.tgz" + integrity sha512-uZk7DevrQLL3vSnfFl5bj4sL75qC9D6EdjemIdbtkuUmIheWpuiiylSY01JxJE7+zGrOWDZrp1WxOuDntvKrHQ== + dependencies: + "@typescript-eslint/types" "7.12.0" + eslint-visitor-keys "^3.4.3" + +"@ungap/structured-clone@^1.2.0": + version "1.2.0" + resolved "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz" + integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== + +"@vitejs/plugin-react-swc@^3.5.0": + version "3.7.0" + resolved "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.7.0.tgz" + integrity sha512-yrknSb3Dci6svCd/qhHqhFPDSw0QtjumcqdKMoNNzmOl5lMXTTiqzjWtG4Qask2HdvvzaNgSunbQGet8/GrKdA== + dependencies: + "@swc/core" "^1.5.7" + +acorn-jsx@^5.3.2: + version "5.3.2" + resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn@^8.9.0: + version "8.11.3" + resolved "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz" + integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== + +ajv@^6.12.4: + version "6.12.6" + resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + 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" + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + +braces@^3.0.3: + version "3.0.3" + resolved "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + dependencies: + fill-range "^7.1.1" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +camelcase-css@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5" + integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== + +chalk@^4.0.0: + version "4.1.2" + resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +clsx@^2.0.0, clsx@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999" + integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA== + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +cross-spawn@^7.0.2: + version "7.0.3" + resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +cssesc@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" + integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== + +csstype@^3.0.2: + version "3.1.3" + resolved "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz" + integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== + +"d3-array@2 - 3", "d3-array@2.10.0 - 3", d3-array@^3.1.6: + version "3.2.4" + resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-3.2.4.tgz#15fec33b237f97ac5d7c986dc77da273a8ed0bb5" + integrity sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg== + dependencies: + internmap "1 - 2" + +"d3-color@1 - 3": + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-3.1.0.tgz#395b2833dfac71507f12ac2f7af23bf819de24e2" + integrity sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA== + +d3-ease@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-3.0.1.tgz#9658ac38a2140d59d346160f1f6c30fda0bd12f4" + integrity sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w== + +"d3-format@1 - 3": + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-3.1.0.tgz#9260e23a28ea5cb109e93b21a06e24e2ebd55641" + integrity sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA== + +"d3-interpolate@1.2.0 - 3", d3-interpolate@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz#3c47aa5b32c5b3dfb56ef3fd4342078a632b400d" + integrity sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g== + dependencies: + d3-color "1 - 3" + +d3-path@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-3.1.0.tgz#22df939032fb5a71ae8b1800d61ddb7851c42526" + integrity sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ== + +d3-scale@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-4.0.2.tgz#82b38e8e8ff7080764f8dcec77bd4be393689396" + integrity sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ== + 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" + +d3-shape@^3.1.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-3.2.0.tgz#a1a839cbd9ba45f28674c69d7f855bcf91dfc6a5" + integrity sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA== + dependencies: + d3-path "^3.1.0" + +"d3-time-format@2 - 4": + version "4.1.0" + resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-4.1.0.tgz#7ab5257a5041d11ecb4fe70a5c7d16a195bb408a" + integrity sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg== + dependencies: + d3-time "1 - 3" + +"d3-time@1 - 3", "d3-time@2.1.1 - 3", d3-time@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-3.1.0.tgz#9310db56e992e3c0175e1ef385e545e48a9bb5c7" + integrity sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q== + dependencies: + d3-array "2 - 3" + +d3-timer@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-3.0.1.tgz#6284d2a2708285b1abb7e201eda4380af35e63b0" + integrity sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA== + +dayjs@^1.11.11: + version "1.11.11" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.11.tgz#dfe0e9d54c5f8b68ccf8ca5f72ac603e7e5ed59e" + integrity sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg== + +debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: + version "4.3.5" + resolved "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz" + integrity sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg== + dependencies: + ms "2.1.2" + +decimal.js-light@^2.4.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/decimal.js-light/-/decimal.js-light-2.5.1.tgz#134fd32508f19e208f4fb2f8dac0d2626a867934" + integrity sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg== + +deep-is@^0.1.3: + 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== + +detect-node-es@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/detect-node-es/-/detect-node-es-1.1.0.tgz#163acdf643330caa0b4cd7c21e7ee7755d6fa493" + integrity sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ== + +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +dom-helpers@^5.0.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902" + integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA== + dependencies: + "@babel/runtime" "^7.8.7" + csstype "^3.0.2" + +esbuild@^0.20.1: + version "0.20.2" + resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz" + integrity sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g== + optionalDependencies: + "@esbuild/aix-ppc64" "0.20.2" + "@esbuild/android-arm" "0.20.2" + "@esbuild/android-arm64" "0.20.2" + "@esbuild/android-x64" "0.20.2" + "@esbuild/darwin-arm64" "0.20.2" + "@esbuild/darwin-x64" "0.20.2" + "@esbuild/freebsd-arm64" "0.20.2" + "@esbuild/freebsd-x64" "0.20.2" + "@esbuild/linux-arm" "0.20.2" + "@esbuild/linux-arm64" "0.20.2" + "@esbuild/linux-ia32" "0.20.2" + "@esbuild/linux-loong64" "0.20.2" + "@esbuild/linux-mips64el" "0.20.2" + "@esbuild/linux-ppc64" "0.20.2" + "@esbuild/linux-riscv64" "0.20.2" + "@esbuild/linux-s390x" "0.20.2" + "@esbuild/linux-x64" "0.20.2" + "@esbuild/netbsd-x64" "0.20.2" + "@esbuild/openbsd-x64" "0.20.2" + "@esbuild/sunos-x64" "0.20.2" + "@esbuild/win32-arm64" "0.20.2" + "@esbuild/win32-ia32" "0.20.2" + "@esbuild/win32-x64" "0.20.2" + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +eslint-plugin-react-hooks@^4.6.0: + 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== + +eslint-plugin-react-refresh@^0.4.6: + version "0.4.7" + resolved "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.7.tgz" + integrity sha512-yrj+KInFmwuQS2UQcg1SF83ha1tuHC1jMQbRNyuWtlEzzKRDgAl7L4Yp4NlDUZTZNlWvHEzOtJhMi40R7JxcSw== + +eslint-scope@^7.2.2: + version "7.2.2" + resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz" + integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + +eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: + 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== + +eslint@^8.57.0: + version "8.57.0" + resolved "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz" + integrity sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ== + 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" + +espree@^9.6.0, espree@^9.6.1: + version "9.6.1" + resolved "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz" + integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== + dependencies: + acorn "^8.9.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^3.4.1" + +esquery@^1.4.2: + version "1.5.0" + resolved "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz" + integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +eventemitter3@^4.0.1: + version "4.0.7" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" + integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== + +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + 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== + +fast-equals@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/fast-equals/-/fast-equals-5.0.1.tgz#a4eefe3c5d1c0d021aeed0bc10ba5e0c12ee405d" + integrity sha512-WF1Wi8PwwSY7/6Kx0vKXtw8RwuSGoM1bvDaJbu7MxDlR1vovZjIAKrnzyrThgAjm6JDTu0fVgWXDlMGspodfoQ== + +fast-glob@^3.2.11, fast-glob@^3.2.9: + version "3.3.2" + resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz" + integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + +fast-json-stable-stringify@^2.0.0: + 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== + +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + +fastq@^1.6.0: + version "1.17.1" + resolved "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz" + integrity sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w== + dependencies: + reusify "^1.0.4" + +file-entry-cache@^6.0.1: + version "6.0.1" + resolved "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz" + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== + dependencies: + flat-cache "^3.0.4" + +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== + dependencies: + to-regex-range "^5.0.1" + +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +flat-cache@^3.0.4: + version "3.2.0" + resolved "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz" + integrity sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw== + dependencies: + flatted "^3.2.9" + keyv "^4.5.3" + rimraf "^3.0.2" + +flatted@^3.2.9: + version "3.3.1" + resolved "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz" + integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +fsevents@~2.3.2, fsevents@~2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +get-nonce@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/get-nonce/-/get-nonce-1.0.1.tgz#fdf3f0278073820d2ce9426c18f07481b1e0cdf3" + integrity sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q== + +glob-parent@^5.1.2: + version "5.1.2" + resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +glob@^7.1.3: + version "7.2.3" + resolved "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + 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" + +globals@^13.19.0: + version "13.24.0" + resolved "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz" + integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ== + dependencies: + type-fest "^0.20.2" + +globby@^11.1.0: + version "11.1.0" + resolved "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz" + integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.2.9" + ignore "^5.2.0" + merge2 "^1.4.1" + slash "^3.0.0" + +graphemer@^1.4.0: + version "1.4.0" + resolved "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz" + integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +highlight.js@^11.9.0: + version "11.9.0" + resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.9.0.tgz#04ab9ee43b52a41a047432c8103e2158a1b8b5b0" + integrity sha512-fJ7cW7fQGCYAkgv4CPfwFHrfd/cLS4Hau96JuJ+ZTOWhjnhoeN1ub1tFmALm/+lW5z4WCAuAV9bm05AP0mS6Gw== + +ignore@^5.2.0, ignore@^5.3.1: + version "5.3.1" + resolved "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz" + integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw== + +import-fresh@^3.2.1: + version "3.3.0" + resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.4" + resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +"internmap@1 - 2": + version "2.0.3" + resolved "https://registry.yarnpkg.com/internmap/-/internmap-2.0.3.tgz#6685f23755e43c524e251d29cbc97248e3061009" + integrity sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg== + +invariant@^2.2.4: + version "2.2.4" + resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" + integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== + dependencies: + loose-envify "^1.0.0" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: + version "4.0.3" + resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-path-inside@^3.0.3: + 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== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +"js-tokens@^3.0.0 || ^4.0.0": + version "4.0.0" + resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-stable-stringify-without-jsonify@^1.0.1: + 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== + +keyv@^4.5.3: + version "4.5.4" + resolved "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz" + integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== + dependencies: + json-buffer "3.0.1" + +klona@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.6.tgz#85bffbf819c03b2f53270412420a4555ef882e22" + integrity sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA== + +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + +lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: + version "1.4.0" + resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + +merge2@^1.3.0, merge2@^1.4.1: + version "1.4.1" + resolved "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +micromatch@^4.0.4: + version "4.0.7" + resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz" + integrity sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q== + dependencies: + braces "^3.0.3" + picomatch "^2.3.1" + +minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: + version "3.1.2" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimatch@^9.0.4: + version "9.0.4" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz" + integrity sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw== + dependencies: + brace-expansion "^2.0.1" + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +nanoid@^3.3.7: + version "3.3.7" + resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz" + integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz" + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== + +object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +optionator@^0.9.3: + version "0.9.4" + resolved "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz" + integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== + 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" + +p-limit@^3.0.2: + version "3.1.0" + resolved "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^5.0.0: + 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== + dependencies: + p-limit "^3.0.2" + +parent-module@^1.0.0: + 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== + dependencies: + callsites "^3.0.0" + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +picocolors@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz" + integrity sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew== + +picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +postcss-js@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-js/-/postcss-js-4.0.1.tgz#61598186f3703bab052f1c4f7d805f3991bee9d2" + integrity sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw== + dependencies: + camelcase-css "^2.0.1" + +postcss-mixins@^9.0.4: + version "9.0.4" + resolved "https://registry.yarnpkg.com/postcss-mixins/-/postcss-mixins-9.0.4.tgz#75cd3cdb619a7e08c4c51ebb094db5f6d65b3831" + integrity sha512-XVq5jwQJDRu5M1XGkdpgASqLk37OqkH4JCFDXl/Dn7janOJjCTEKL+36cnRVy7bMtoBzALfO7bV7nTIsFnUWLA== + dependencies: + fast-glob "^3.2.11" + postcss-js "^4.0.0" + postcss-simple-vars "^7.0.0" + sugarss "^4.0.1" + +postcss-nested@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-6.0.1.tgz#f83dc9846ca16d2f4fa864f16e9d9f7d0961662c" + integrity sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ== + dependencies: + postcss-selector-parser "^6.0.11" + +postcss-preset-mantine@^1.15.0: + version "1.15.0" + resolved "https://registry.yarnpkg.com/postcss-preset-mantine/-/postcss-preset-mantine-1.15.0.tgz#011b4770f185c10256bea65b8b0999a2e2d64cc0" + integrity sha512-OKPs6uoORSXlU/GFH1ZtFaslecHBPwuoSikdL5W3WKJm4ZPAQM0mw9x9m3toa/Mo1JhoBmYMM28i+zEdav5Edg== + dependencies: + postcss-mixins "^9.0.4" + postcss-nested "^6.0.1" + +postcss-selector-parser@^6.0.11: + version "6.1.0" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.1.0.tgz#49694cb4e7c649299fea510a29fa6577104bcf53" + integrity sha512-UMz42UD0UY0EApS0ZL9o1XnLhSTtvvvLe5Dc2H2O56fvRZi+KulDyf5ctDhhtYJBGKStV2FL1fy6253cmLgqVQ== + dependencies: + cssesc "^3.0.0" + util-deprecate "^1.0.2" + +postcss-simple-vars@^7.0.0, postcss-simple-vars@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/postcss-simple-vars/-/postcss-simple-vars-7.0.1.tgz#836b3097a54dcd13dbd3c36a5dbdd512fad2954c" + integrity sha512-5GLLXaS8qmzHMOjVxqkk1TZPf1jMqesiI7qLhnlyERalG0sMbHIbJqrcnrpmZdKCLglHnRHoEBB61RtGTsj++A== + +postcss@^8.4.38: + version "8.4.38" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.38.tgz#b387d533baf2054288e337066d81c6bee9db9e0e" + integrity sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A== + dependencies: + nanoid "^3.3.7" + picocolors "^1.0.0" + source-map-js "^1.2.0" + +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: + version "15.8.1" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" + integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== + dependencies: + loose-envify "^1.4.0" + object-assign "^4.1.1" + react-is "^16.13.1" + +punycode@^2.1.0: + version "2.3.1" + resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +react-dom@^18.2.0: + version "18.3.1" + resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz" + integrity sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw== + dependencies: + loose-envify "^1.1.0" + scheduler "^0.23.2" + +react-is@^16.10.2, react-is@^16.13.1: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" + integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== + +react-number-format@^5.3.1: + version "5.4.0" + resolved "https://registry.yarnpkg.com/react-number-format/-/react-number-format-5.4.0.tgz#8c1e97add1970d1a2f372ca286bcdaa49632ba5c" + integrity sha512-NWdICrqLhI7rAS8yUeLVd6Wr4cN7UjJ9IBTS0f/a9i7UB4x4Ti70kGnksBtZ7o4Z7YRbvCMMR/jQmkoOBa/4fg== + dependencies: + prop-types "^15.7.2" + +react-remove-scroll-bar@^2.3.6: + version "2.3.6" + resolved "https://registry.yarnpkg.com/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.6.tgz#3e585e9d163be84a010180b18721e851ac81a29c" + integrity sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g== + dependencies: + react-style-singleton "^2.2.1" + tslib "^2.0.0" + +react-remove-scroll@^2.5.7: + version "2.5.10" + resolved "https://registry.yarnpkg.com/react-remove-scroll/-/react-remove-scroll-2.5.10.tgz#5fae456a23962af6d3c38ca1978bcfe0806c4061" + integrity sha512-m3zvBRANPBw3qxVVjEIPEQinkcwlFZ4qyomuWVpNJdv4c6MvHfXV0C3L9Jx5rr3HeBHKNRX+1jreB5QloDIJjA== + dependencies: + react-remove-scroll-bar "^2.3.6" + react-style-singleton "^2.2.1" + tslib "^2.1.0" + use-callback-ref "^1.3.0" + use-sidecar "^1.1.2" + +react-router-dom@^6.23.1: + version "6.23.1" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.23.1.tgz#30cbf266669693e9492aa4fc0dde2541ab02322f" + integrity sha512-utP+K+aSTtEdbWpC+4gxhdlPFwuEfDKq8ZrPFU65bbRJY+l706qjR7yaidBpo3MSeA/fzwbXWbKBI6ftOnP3OQ== + dependencies: + "@remix-run/router" "1.16.1" + react-router "6.23.1" + +react-router@6.23.1: + version "6.23.1" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.23.1.tgz#d08cbdbd9d6aedc13eea6e94bc6d9b29cb1c4be9" + integrity sha512-fzcOaRF69uvqbbM7OhvQyBTFDVrrGlsFdS3AL+1KfIBtGETibHzi3FkoTRyiDJnWNc2VxrfvR+657ROHjaNjqQ== + dependencies: + "@remix-run/router" "1.16.1" + +react-smooth@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/react-smooth/-/react-smooth-4.0.1.tgz#6200d8699bfe051ae40ba187988323b1449eab1a" + integrity sha512-OE4hm7XqR0jNOq3Qmk9mFLyd6p2+j6bvbPJ7qlB7+oo0eNcL2l7WQzG6MBnT3EXY6xzkLMUBec3AfewJdA0J8w== + dependencies: + fast-equals "^5.0.1" + prop-types "^15.8.1" + react-transition-group "^4.4.5" + +react-style-singleton@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/react-style-singleton/-/react-style-singleton-2.2.1.tgz#f99e420492b2d8f34d38308ff660b60d0b1205b4" + integrity sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g== + dependencies: + get-nonce "^1.0.0" + invariant "^2.2.4" + tslib "^2.0.0" + +react-textarea-autosize@8.5.3: + version "8.5.3" + resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-8.5.3.tgz#d1e9fe760178413891484847d3378706052dd409" + integrity sha512-XT1024o2pqCuZSuBt9FwHlaDeNtVrtCXu0Rnz88t1jUGheCLa3PhjE1GH8Ctm2axEtvdCl5SUHYschyQ0L5QHQ== + dependencies: + "@babel/runtime" "^7.20.13" + use-composed-ref "^1.3.0" + use-latest "^1.2.1" + +react-transition-group@4.4.5, react-transition-group@^4.4.5: + version "4.4.5" + resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1" + integrity sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g== + dependencies: + "@babel/runtime" "^7.5.5" + dom-helpers "^5.0.1" + loose-envify "^1.4.0" + prop-types "^15.6.2" + +react@^18.2.0: + 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== + dependencies: + loose-envify "^1.1.0" + +recharts-scale@^0.4.4: + version "0.4.5" + resolved "https://registry.yarnpkg.com/recharts-scale/-/recharts-scale-0.4.5.tgz#0969271f14e732e642fcc5bd4ab270d6e87dd1d9" + integrity sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w== + dependencies: + decimal.js-light "^2.4.1" + +recharts@2: + version "2.12.7" + resolved "https://registry.yarnpkg.com/recharts/-/recharts-2.12.7.tgz#c7f42f473a257ff88b43d88a92530930b5f9e773" + integrity sha512-hlLJMhPQfv4/3NBSAyq3gzGg4h2v69RJh6KU7b3pXYNNAELs9kEoXOjbkxdXpALqKBoVmVptGfLpxdaVYqjmXQ== + dependencies: + clsx "^2.0.0" + eventemitter3 "^4.0.1" + lodash "^4.17.21" + react-is "^16.10.2" + react-smooth "^4.0.0" + recharts-scale "^0.4.4" + tiny-invariant "^1.3.1" + victory-vendor "^36.6.8" + +regenerator-runtime@^0.14.0: + version "0.14.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f" + integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw== + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +rollup@^4.13.0: + version "4.18.0" + resolved "https://registry.npmjs.org/rollup/-/rollup-4.18.0.tgz" + integrity sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg== + dependencies: + "@types/estree" "1.0.5" + optionalDependencies: + "@rollup/rollup-android-arm-eabi" "4.18.0" + "@rollup/rollup-android-arm64" "4.18.0" + "@rollup/rollup-darwin-arm64" "4.18.0" + "@rollup/rollup-darwin-x64" "4.18.0" + "@rollup/rollup-linux-arm-gnueabihf" "4.18.0" + "@rollup/rollup-linux-arm-musleabihf" "4.18.0" + "@rollup/rollup-linux-arm64-gnu" "4.18.0" + "@rollup/rollup-linux-arm64-musl" "4.18.0" + "@rollup/rollup-linux-powerpc64le-gnu" "4.18.0" + "@rollup/rollup-linux-riscv64-gnu" "4.18.0" + "@rollup/rollup-linux-s390x-gnu" "4.18.0" + "@rollup/rollup-linux-x64-gnu" "4.18.0" + "@rollup/rollup-linux-x64-musl" "4.18.0" + "@rollup/rollup-win32-arm64-msvc" "4.18.0" + "@rollup/rollup-win32-ia32-msvc" "4.18.0" + "@rollup/rollup-win32-x64-msvc" "4.18.0" + fsevents "~2.3.2" + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +scheduler@^0.23.2: + version "0.23.2" + resolved "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz" + integrity sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ== + dependencies: + loose-envify "^1.1.0" + +semver@^7.6.0: + version "7.6.2" + resolved "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz" + integrity sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w== + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +source-map-js@^1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz" + integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg== + +strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-json-comments@^3.1.1: + 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== + +sugarss@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/sugarss/-/sugarss-4.0.1.tgz#128a783ed71ee0fc3b489ce1f7d5a89bc1e24383" + integrity sha512-WCjS5NfuVJjkQzK10s8WOBY+hhDxxNt/N6ZaGwxFZ+wN3/lKKFSaaKUNecULcTTvE4urLcKaZFQD8vO0mOZujw== + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +tabbable@^6.0.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-6.2.0.tgz#732fb62bc0175cfcec257330be187dcfba1f3b97" + integrity sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew== + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz" + integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== + +tiny-invariant@^1.3.1: + version "1.3.3" + resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz#46680b7a873a0d5d10005995eb90a70d74d60127" + integrity sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg== + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +ts-api-utils@^1.3.0: + version "1.3.0" + resolved "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz" + integrity sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ== + +tslib@^2.0.0, tslib@^2.1.0: + version "2.6.3" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.3.tgz#0438f810ad7a9edcde7a241c3d80db693c8cbfe0" + integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ== + +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + +type-fest@^0.20.2: + 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== + +type-fest@^4.12.0: + version "4.19.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.19.0.tgz#f7d3d5f55a7a118b5fe3d2eef53059cf8e516dcd" + integrity sha512-CN2l+hWACRiejlnr68vY0/7734Kzu+9+TOslUXbSCQ1ruY9XIHDBSceVXCcHm/oXrdzhtLMMdJEKfemf1yXiZQ== + +typescript@^5.2.2: + version "5.4.5" + resolved "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz" + integrity sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ== + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +use-callback-ref@^1.3.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.3.2.tgz#6134c7f6ff76e2be0b56c809b17a650c942b1693" + integrity sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA== + dependencies: + tslib "^2.0.0" + +use-composed-ref@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/use-composed-ref/-/use-composed-ref-1.3.0.tgz#3d8104db34b7b264030a9d916c5e94fbe280dbda" + integrity sha512-GLMG0Jc/jiKov/3Ulid1wbv3r54K9HlMW29IWcDFPEqFkSO2nS0MuefWgMJpeHQ9YJeXDL3ZUF+P3jdXlZX/cQ== + +use-isomorphic-layout-effect@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz#497cefb13d863d687b08477d9e5a164ad8c1a6fb" + integrity sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA== + +use-latest@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/use-latest/-/use-latest-1.2.1.tgz#d13dfb4b08c28e3e33991546a2cee53e14038cf2" + integrity sha512-xA+AVm/Wlg3e2P/JiItTziwS7FK92LWrDB0p+hgXloIMuVCeJJ8v6f0eeHyPZaJrM+usM1FkFfbNCrJGs8A/zw== + dependencies: + use-isomorphic-layout-effect "^1.1.1" + +use-sidecar@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/use-sidecar/-/use-sidecar-1.1.2.tgz#2f43126ba2d7d7e117aa5855e5d8f0276dfe73c2" + integrity sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw== + dependencies: + detect-node-es "^1.1.0" + tslib "^2.0.0" + +util-deprecate@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + +victory-vendor@^36.6.8: + version "36.9.2" + resolved "https://registry.yarnpkg.com/victory-vendor/-/victory-vendor-36.9.2.tgz#668b02a448fa4ea0f788dbf4228b7e64669ff801" + integrity sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ== + 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" + +vite@^5.2.0: + version "5.2.12" + resolved "https://registry.npmjs.org/vite/-/vite-5.2.12.tgz" + integrity sha512-/gC8GxzxMK5ntBwb48pR32GGhENnjtY30G4A0jemunsBkiEZFw60s8InGpN8gkhHEkjnRK1aSAxeQgwvFhUHAA== + dependencies: + esbuild "^0.20.1" + postcss "^8.4.38" + rollup "^4.13.0" + optionalDependencies: + fsevents "~2.3.3" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +word-wrap@^1.2.5: + version "1.2.5" + resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz" + integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== + +wrappy@1: + version "1.0.2" + resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== From 2ec0d0b7113e539bed394aa56db5e8d545d4746f Mon Sep 17 00:00:00 2001 From: Treyson Date: Wed, 3 Jul 2024 12:08:21 -0600 Subject: [PATCH 02/96] Initial Demo for login works, some issues fixed by PRG pattern lol --- .gitignore | 2 + package-lock.json | 3725 +++++++++++++++++++++++++++++++++++++++++ package.json | 4 +- src/App.css | 42 - src/App.tsx | 40 +- src/ConfigContext.tsx | 26 + src/assets/react.svg | 1 - src/config.ts | 11 + src/index.css | 68 - src/pages/Home.tsx | 18 + src/pages/Login.tsx | 138 ++ tsconfig.json | 20 +- 12 files changed, 3951 insertions(+), 144 deletions(-) create mode 100644 package-lock.json delete mode 100644 src/App.css create mode 100644 src/ConfigContext.tsx delete mode 100644 src/assets/react.svg create mode 100644 src/config.ts create mode 100644 src/pages/Home.tsx diff --git a/.gitignore b/.gitignore index a547bf3..2bd1803 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +.env + # Logs logs *.log diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..32a1661 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3725 @@ +{ + "name": "vite-openipam-frontend", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "vite-openipam-frontend", + "version": "0.0.0", + "dependencies": { + "@mantine/charts": "^7.10.1", + "@mantine/code-highlight": "^7.10.1", + "@mantine/core": "^7.10.1", + "@mantine/dates": "^7.10.1", + "@mantine/form": "^7.10.1", + "@mantine/hooks": "^7.10.1", + "@mantine/modals": "^7.10.1", + "@mantine/notifications": "^7.10.1", + "@mantine/nprogress": "^7.10.1", + "@mantine/spotlight": "^7.10.1", + "dayjs": "^1.11.11", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-router-dom": "^6.24.1", + "recharts": "2" + }, + "devDependencies": { + "@types/node": "^20.14.9", + "@types/react": "^18.2.66", + "@types/react-dom": "^18.2.22", + "@typescript-eslint/eslint-plugin": "^7.2.0", + "@typescript-eslint/parser": "^7.2.0", + "@vitejs/plugin-react-swc": "^3.5.0", + "dotenv": "^16.4.5", + "eslint": "^8.57.0", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.6", + "postcss": "^8.4.38", + "postcss-preset-mantine": "^1.15.0", + "postcss-simple-vars": "^7.0.1", + "typescript": "^5.2.2", + "vite": "^5.2.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.7.tgz", + "integrity": "sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", + "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", + "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", + "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", + "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", + "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", + "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", + "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", + "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", + "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", + "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", + "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", + "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", + "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", + "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", + "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", + "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", + "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", + "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", + "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", + "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", + "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", + "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", + "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "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, + "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.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.1.tgz", + "integrity": "sha512-Zm2NGpWELsQAD1xsJzGQpYfvICSsFkEpU0jxBjfdC6uNEWXcHnfs9hScFWtXVDVl+rBQJGrl4g1vcKIejpH9dA==", + "dev": true, + "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, + "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/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, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/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, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "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, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.2.tgz", + "integrity": "sha512-+2XpQV9LLZeanU4ZevzRnGFg2neDeKHgFLjP6YLW+tly0IvrhqT4u8enLGjLH3qeh85g19xY5rsAusfwTdn5lg==", + "dependencies": { + "@floating-ui/utils": "^0.2.0" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.5.tgz", + "integrity": "sha512-Nsdud2X65Dz+1RHjAIP0t8z5e2ff/IRbei6BqFrl1urT8sDVzM1HMQ+R0XcU5ceRfyO3I6ayeqIfh+6Wb8LGTw==", + "dependencies": { + "@floating-ui/core": "^1.0.0", + "@floating-ui/utils": "^0.2.0" + } + }, + "node_modules/@floating-ui/react": { + "version": "0.26.16", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.16.tgz", + "integrity": "sha512-HEf43zxZNAI/E781QIVpYSF3K2VH4TTYZpqecjdsFkjsaU1EbaWcM++kw0HXFffj7gDUcBFevX8s0rQGQpxkow==", + "dependencies": { + "@floating-ui/react-dom": "^2.1.0", + "@floating-ui/utils": "^0.2.0", + "tabbable": "^6.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.0.tgz", + "integrity": "sha512-lNzj5EQmEKn5FFKc04+zasr09h/uX8RtJRNj5gUXsSQIXHVWTVh+hVAg1vOMCexkX8EgvemMvIFpQfkosnVNyA==", + "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.2", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.2.tgz", + "integrity": "sha512-J4yDIIthosAsRZ5CPYP/jQvUAQtlZTTD/4suA08/FEnlxqW3sKS9iAhgsa9VYLZ6vDHn/ixJgIqRQPotoBjxIw==" + }, + "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==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/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, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/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, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "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, + "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==", + "dev": true + }, + "node_modules/@mantine/charts": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@mantine/charts/-/charts-7.10.1.tgz", + "integrity": "sha512-fMy2EmgegdHVkrtnRO8ync8kqOJoXqixxc1JDmhn9tks4lSvKAKB9ui8OyaTUOWzls/Cs0VfyW51sB/HKe5faw==", + "peerDependencies": { + "@mantine/core": "7.10.1", + "@mantine/hooks": "7.10.1", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "recharts": "^2.10.3" + } + }, + "node_modules/@mantine/code-highlight": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@mantine/code-highlight/-/code-highlight-7.10.1.tgz", + "integrity": "sha512-ZeqBnd/i6CNF8avmjgYNqo9hKFnrzYoKV13OrKAHNRZk7vdbBGoSeVYF1vq+ChDBOZQfLOW+2naTi3VdzwLOhg==", + "dependencies": { + "clsx": "^2.1.1", + "highlight.js": "^11.9.0" + }, + "peerDependencies": { + "@mantine/core": "7.10.1", + "@mantine/hooks": "7.10.1", + "react": "^18.2.0", + "react-dom": "^18.2.0" + } + }, + "node_modules/@mantine/core": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@mantine/core/-/core-7.10.1.tgz", + "integrity": "sha512-l9ypojKN3PjwO1CSLIsqxi7mA25+7w+xc71Q+JuCCREI0tuGwkZsKbIOpuTATIJOjPh8ycLiW7QxX1LYsRTq6w==", + "dependencies": { + "@floating-ui/react": "^0.26.9", + "clsx": "^2.1.1", + "react-number-format": "^5.3.1", + "react-remove-scroll": "^2.5.7", + "react-textarea-autosize": "8.5.3", + "type-fest": "^4.12.0" + }, + "peerDependencies": { + "@mantine/hooks": "7.10.1", + "react": "^18.2.0", + "react-dom": "^18.2.0" + } + }, + "node_modules/@mantine/dates": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@mantine/dates/-/dates-7.10.1.tgz", + "integrity": "sha512-XkzYaHgzJPrquG78/exJd0dLeghJvRgypfSRwFH7IcR34Eo2MD3nsuorZp09i9sYnROcXqIFhZWgwk5cqg/8nw==", + "dependencies": { + "clsx": "^2.1.1" + }, + "peerDependencies": { + "@mantine/core": "7.10.1", + "@mantine/hooks": "7.10.1", + "dayjs": ">=1.0.0", + "react": "^18.2.0", + "react-dom": "^18.2.0" + } + }, + "node_modules/@mantine/form": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@mantine/form/-/form-7.10.1.tgz", + "integrity": "sha512-mZwzg4GEWKEDKEIZu9FmSpGFzYYhFD2YArVOXUM0MMciUqX7yxSCon1PaPJxrV8ldc6FE+JLVI2+G2KVxJ3ZXA==", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "klona": "^2.0.6" + }, + "peerDependencies": { + "react": "^18.2.0" + } + }, + "node_modules/@mantine/hooks": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-7.10.1.tgz", + "integrity": "sha512-0EH9WBWUdtQLGU3Ak+csQ77EtUxI6pPNfwZdRJQWcaA3f8SFOLo9h9CGxiikFExerhvuCeUlaTf3s+TB9Op/rw==", + "peerDependencies": { + "react": "^18.2.0" + } + }, + "node_modules/@mantine/modals": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@mantine/modals/-/modals-7.10.1.tgz", + "integrity": "sha512-2riQSNpVV7f0baizlqcggz9hx9/+y6SQTnW3zEkl/RIkuyK9dpeMFUG6M+M8ntwP79b7x9n7Em9PMWxRbgi28A==", + "peerDependencies": { + "@mantine/core": "7.10.1", + "@mantine/hooks": "7.10.1", + "react": "^18.2.0", + "react-dom": "^18.2.0" + } + }, + "node_modules/@mantine/notifications": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@mantine/notifications/-/notifications-7.10.1.tgz", + "integrity": "sha512-cx3JR3BJzEzH6t2EF1ysrWVY/rdJk0WbSBQo/qFamJd2sbU+8XAHriI8Cx6hNo7uRGCwd8VGAj7Cf3aWK2VC5A==", + "dependencies": { + "@mantine/store": "7.10.1", + "react-transition-group": "4.4.5" + }, + "peerDependencies": { + "@mantine/core": "7.10.1", + "@mantine/hooks": "7.10.1", + "react": "^18.2.0", + "react-dom": "^18.2.0" + } + }, + "node_modules/@mantine/nprogress": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@mantine/nprogress/-/nprogress-7.10.1.tgz", + "integrity": "sha512-irzVf35N8YQJEM5TwlzF0lZeguHeMejN005IMFg2UvlMw8ba+WiXGlK4vELhCq0+37yDqRsxl8NKf6crQiNVRQ==", + "dependencies": { + "@mantine/store": "7.10.1" + }, + "peerDependencies": { + "@mantine/core": "7.10.1", + "@mantine/hooks": "7.10.1", + "react": "^18.2.0", + "react-dom": "^18.2.0" + } + }, + "node_modules/@mantine/spotlight": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@mantine/spotlight/-/spotlight-7.10.1.tgz", + "integrity": "sha512-0G9HLaSbgJvSZRFIsEPyLUPM9heLBsYsKNYAeh6gTE6Q0/rdc5WrXxPUyOzdUaZVu4JVPvdzx/Ov5fa+kGgU4g==", + "dependencies": { + "@mantine/store": "7.10.1" + }, + "peerDependencies": { + "@mantine/core": "7.10.1", + "@mantine/hooks": "7.10.1", + "react": "^18.2.0", + "react-dom": "^18.2.0" + } + }, + "node_modules/@mantine/store": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@mantine/store/-/store-7.10.1.tgz", + "integrity": "sha512-KrGBsSoMsfrYeLxPwf5rFv0s2Nl/4wf+AaF/U1SpQrMgPI8vYokPXx52Wp3jCmlo12NCZnCIG+/6YHAdTWH1qQ==", + "peerDependencies": { + "react": "^18.2.0" + } + }, + "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, + "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, + "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, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@remix-run/router": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.17.1.tgz", + "integrity": "sha512-mCOMec4BKd6BRGBZeSnGiIgwsbLGp3yhVqAD8H+PxiRNEHgDpZb8J1TnrSDlg97t0ySKMQJTHCWBCmBpSmkF6Q==", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.0.tgz", + "integrity": "sha512-Tya6xypR10giZV1XzxmH5wr25VcZSncG0pZIjfePT0OVBvqNEurzValetGNarVrGiq66EBVAFn15iYX4w6FKgQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.18.0.tgz", + "integrity": "sha512-avCea0RAP03lTsDhEyfy+hpfr85KfyTctMADqHVhLAF3MlIkq83CP8UfAHUssgXTYd+6er6PaAhx/QGv4L1EiA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.18.0.tgz", + "integrity": "sha512-IWfdwU7KDSm07Ty0PuA/W2JYoZ4iTj3TUQjkVsO/6U+4I1jN5lcR71ZEvRh52sDOERdnNhhHU57UITXz5jC1/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.18.0.tgz", + "integrity": "sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.18.0.tgz", + "integrity": "sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.18.0.tgz", + "integrity": "sha512-l3m9ewPgjQSXrUMHg93vt0hYCGnrMOcUpTz6FLtbwljo2HluS4zTXFy2571YQbisTnfTKPZ01u/ukJdQTLGh9A==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.18.0.tgz", + "integrity": "sha512-rJ5D47d8WD7J+7STKdCUAgmQk49xuFrRi9pZkWoRD1UeSMakbcepWXPF8ycChBoAqs1pb2wzvbY6Q33WmN2ftw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.18.0.tgz", + "integrity": "sha512-be6Yx37b24ZwxQ+wOQXXLZqpq4jTckJhtGlWGZs68TgdKXJgw54lUUoFYrg6Zs/kjzAQwEwYbp8JxZVzZLRepQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.18.0.tgz", + "integrity": "sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.18.0.tgz", + "integrity": "sha512-ROCM7i+m1NfdrsmvwSzoxp9HFtmKGHEqu5NNDiZWQtXLA8S5HBCkVvKAxJ8U+CVctHwV2Gb5VUaK7UAkzhDjlg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.18.0.tgz", + "integrity": "sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.18.0.tgz", + "integrity": "sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.18.0.tgz", + "integrity": "sha512-LKaqQL9osY/ir2geuLVvRRs+utWUNilzdE90TpyoX0eNqPzWjRm14oMEE+YLve4k/NAqCdPkGYDaDF5Sw+xBfg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.18.0.tgz", + "integrity": "sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.18.0.tgz", + "integrity": "sha512-Txjh+IxBPbkUB9+SXZMpv+b/vnTEtFyfWZgJ6iyCmt2tdx0OF5WhFowLmnh8ENGNpfUlUZkdI//4IEmhwPieNg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.0.tgz", + "integrity": "sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@swc/core": { + "version": "1.5.25", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.5.25.tgz", + "integrity": "sha512-qdGEIdLVoTjEQ7w72UyyQ0wLFY4XbHfZiidmPHKJQsvSXzdpHXxPdlTCea/mY4AhMqo/M+pvkJSXJAxZnFl7qw==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "@swc/counter": "^0.1.3", + "@swc/types": "^0.1.7" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/swc" + }, + "optionalDependencies": { + "@swc/core-darwin-arm64": "1.5.25", + "@swc/core-darwin-x64": "1.5.25", + "@swc/core-linux-arm-gnueabihf": "1.5.25", + "@swc/core-linux-arm64-gnu": "1.5.25", + "@swc/core-linux-arm64-musl": "1.5.25", + "@swc/core-linux-x64-gnu": "1.5.25", + "@swc/core-linux-x64-musl": "1.5.25", + "@swc/core-win32-arm64-msvc": "1.5.25", + "@swc/core-win32-ia32-msvc": "1.5.25", + "@swc/core-win32-x64-msvc": "1.5.25" + }, + "peerDependencies": { + "@swc/helpers": "*" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/@swc/core-darwin-arm64": { + "version": "1.5.25", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.5.25.tgz", + "integrity": "sha512-YbD0SBgVJS2DM0vwJTU5m7+wOyCjHPBDMf3nCBJQzFZzOLzK11eRW7SzU2jhJHr9HI9sKcNFfN4lIC2Sj+4inA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-darwin-x64": { + "version": "1.5.25", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.5.25.tgz", + "integrity": "sha512-OhP4TROT6gQuozn+ah0Y4UidSdgDmxwtQq3lgCUIAxJYErJAQ82/Y0kve2UaNmkSGjOHU+/b4siHPrYTkXOk0Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.5.25", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.5.25.tgz", + "integrity": "sha512-tNmUfrAHxN2gvYPyYNnHx2CYlPO7DGAUuK/bZrqawu++djcg+atAV3eI3XYJgmHId7/sYAlDQ9wjkrOLofFjVg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.5.25", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.5.25.tgz", + "integrity": "sha512-stzpke+bRaNFM/HrZPRjX0aQZ86S/2DChVCwb8NAV1n5lu9mz1CS750y7WbbtX/KZjk92FsCeRy2qwkvjI0gWw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.5.25", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.5.25.tgz", + "integrity": "sha512-UckUfDYedish/bj2V1jgQDGgouLhyRpG7jgF3mp8jHir11V2K6JiTyjFoz99eOiclS3+hNdr4QLJ+ifrQMJNZw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.5.25", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.5.25.tgz", + "integrity": "sha512-LwbJEgNT3lXbvz4WFzVNXNvs8DvxpoXjMZk9K9Hig8tmZQJKHC2qZTGomcyK5EFzfj2HBuBXZnAEW8ZT9PcEaA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.5.25", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.5.25.tgz", + "integrity": "sha512-rsepMTgml0EkswWkBpg3Wrjj5eqjwTzZN5omAn1klzXSZnClTrfeHvBuoIJYVr1yx+jmBkqySgME2p7+magUAw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.5.25", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.5.25.tgz", + "integrity": "sha512-DJDsLBsRBV3uQBShRK2x6fqzABp9RLNVxDUpTTvUjc7qywJ8vS/yn+POK/zCyVEqLagf1z/8D5CEQ+RAIJq1NA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.5.25", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.5.25.tgz", + "integrity": "sha512-BARL1ulHol53MEKC1ZVWM3A3FP757UUgG5Q8v97za+4a1SaIgbwvAQyHDxMYWi9+ij+OapK8YnWjJcFa17g8dw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.5.25", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.5.25.tgz", + "integrity": "sha512-o+MHUWrQI9iR6EusEV8eNU2Ezi3KtlhUR4gfptQN5MbVzlgjTvQbhiKpE1GYOxp+0BLBbKRwITKOcdhxfEJ2Uw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "dev": true + }, + "node_modules/@swc/types": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.7.tgz", + "integrity": "sha512-scHWahbHF0eyj3JsxG9CFJgFdFNaVQCNAimBlT6PzS3n/HptxqREjsm4OH6AN3lYcffZYSPxXW8ua2BEHp0lJQ==", + "dev": true, + "dependencies": { + "@swc/counter": "^0.1.3" + } + }, + "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==" + }, + "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==" + }, + "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==" + }, + "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==", + "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==" + }, + "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==", + "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==", + "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==" + }, + "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==" + }, + "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==", + "dev": true + }, + "node_modules/@types/node": { + "version": "20.14.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.9.tgz", + "integrity": "sha512-06OCtnTXtWOZBJlRApleWndH4JsRVs1pDCc8dLSQp+7PpUpX3ePdHyeNSFTeSe7FtKyQkrlPvHwJOW3SLd8Oyg==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "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==", + "devOptional": true + }, + "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==", + "devOptional": true, + "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, + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.12.0.tgz", + "integrity": "sha512-7F91fcbuDf/d3S8o21+r3ZncGIke/+eWk0EpO21LXhDfLahriZF9CGj4fbAetEjlaBdjdSm9a6VeXbpbT6Z40Q==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "7.12.0", + "@typescript-eslint/type-utils": "7.12.0", + "@typescript-eslint/utils": "7.12.0", + "@typescript-eslint/visitor-keys": "7.12.0", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.12.0.tgz", + "integrity": "sha512-dm/J2UDY3oV3TKius2OUZIFHsomQmpHtsV0FTh1WO8EKgHLQ1QCADUqscPgTpU+ih1e21FQSRjXckHn3txn6kQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "7.12.0", + "@typescript-eslint/types": "7.12.0", + "@typescript-eslint/typescript-estree": "7.12.0", + "@typescript-eslint/visitor-keys": "7.12.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.12.0.tgz", + "integrity": "sha512-itF1pTnN6F3unPak+kutH9raIkL3lhH1YRPGgt7QQOh43DQKVJXmWkpb+vpc/TiDHs6RSd9CTbDsc/Y+Ygq7kg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.12.0", + "@typescript-eslint/visitor-keys": "7.12.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.12.0.tgz", + "integrity": "sha512-lib96tyRtMhLxwauDWUp/uW3FMhLA6D0rJ8T7HmH7x23Gk1Gwwu8UZ94NMXBvOELn6flSPiBrCKlehkiXyaqwA==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "7.12.0", + "@typescript-eslint/utils": "7.12.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.12.0.tgz", + "integrity": "sha512-o+0Te6eWp2ppKY3mLCU+YA9pVJxhUJE15FV7kxuD9jgwIAa+w/ycGJBMrYDTpVGUM/tgpa9SeMOugSabWFq7bg==", + "dev": true, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.12.0.tgz", + "integrity": "sha512-5bwqLsWBULv1h6pn7cMW5dXX/Y2amRqLaKqsASVwbBHMZSnHqE/HN4vT4fE0aFsiwxYvr98kqOWh1a8ZKXalCQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.12.0", + "@typescript-eslint/visitor-keys": "7.12.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.12.0.tgz", + "integrity": "sha512-Y6hhwxwDx41HNpjuYswYp6gDbkiZ8Hin9Bf5aJQn1bpTs3afYY4GX+MPYxma8jtoIV2GRwTM/UJm/2uGCVv+DQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "7.12.0", + "@typescript-eslint/types": "7.12.0", + "@typescript-eslint/typescript-estree": "7.12.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.12.0.tgz", + "integrity": "sha512-uZk7DevrQLL3vSnfFl5bj4sL75qC9D6EdjemIdbtkuUmIheWpuiiylSY01JxJE7+zGrOWDZrp1WxOuDntvKrHQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.12.0", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "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 + }, + "node_modules/@vitejs/plugin-react-swc": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.7.0.tgz", + "integrity": "sha512-yrknSb3Dci6svCd/qhHqhFPDSw0QtjumcqdKMoNNzmOl5lMXTTiqzjWtG4Qask2HdvvzaNgSunbQGet8/GrKdA==", + "dev": true, + "dependencies": { + "@swc/core": "^1.5.7" + }, + "peerDependencies": { + "vite": "^4 || ^5" + } + }, + "node_modules/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "dev": true, + "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, + "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, + "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, + "engines": { + "node": ">=8" + } + }, + "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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "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==", + "dev": true + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "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 + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "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, + "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/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "engines": { + "node": ">=6" + } + }, + "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, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "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 + }, + "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 + }, + "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, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + }, + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "engines": { + "node": ">=12" + } + }, + "node_modules/dayjs": { + "version": "1.11.11", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.11.tgz", + "integrity": "sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg==" + }, + "node_modules/debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "dev": true, + "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==" + }, + "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 + }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==" + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "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==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "node_modules/dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/esbuild": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", + "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.20.2", + "@esbuild/android-arm": "0.20.2", + "@esbuild/android-arm64": "0.20.2", + "@esbuild/android-x64": "0.20.2", + "@esbuild/darwin-arm64": "0.20.2", + "@esbuild/darwin-x64": "0.20.2", + "@esbuild/freebsd-arm64": "0.20.2", + "@esbuild/freebsd-x64": "0.20.2", + "@esbuild/linux-arm": "0.20.2", + "@esbuild/linux-arm64": "0.20.2", + "@esbuild/linux-ia32": "0.20.2", + "@esbuild/linux-loong64": "0.20.2", + "@esbuild/linux-mips64el": "0.20.2", + "@esbuild/linux-ppc64": "0.20.2", + "@esbuild/linux-riscv64": "0.20.2", + "@esbuild/linux-s390x": "0.20.2", + "@esbuild/linux-x64": "0.20.2", + "@esbuild/netbsd-x64": "0.20.2", + "@esbuild/openbsd-x64": "0.20.2", + "@esbuild/sunos-x64": "0.20.2", + "@esbuild/win32-arm64": "0.20.2", + "@esbuild/win32-ia32": "0.20.2", + "@esbuild/win32-x64": "0.20.2" + } + }, + "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==", + "dev": true, + "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, + "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-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, + "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.7", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.7.tgz", + "integrity": "sha512-yrj+KInFmwuQS2UQcg1SF83ha1tuHC1jMQbRNyuWtlEzzKRDgAl7L4Yp4NlDUZTZNlWvHEzOtJhMi40R7JxcSw==", + "dev": true, + "peerDependencies": { + "eslint": ">=7" + } + }, + "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, + "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, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/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, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/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, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "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, + "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.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "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, + "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, + "engines": { + "node": ">=4.0" + } + }, + "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, + "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==" + }, + "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==" + }, + "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==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "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 + }, + "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 + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dev": true, + "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, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "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, + "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 + }, + "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 + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "engines": { + "node": ">=6" + } + }, + "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, + "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, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/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, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/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, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "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, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globals/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, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "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, + "engines": { + "node": ">=8" + } + }, + "node_modules/highlight.js": { + "version": "11.9.0", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.9.0.tgz", + "integrity": "sha512-fJ7cW7fQGCYAkgv4CPfwFHrfd/cLS4Hau96JuJ+ZTOWhjnhoeN1ub1tFmALm/+lW5z4WCAuAV9bm05AP0mS6Gw==", + "engines": { + "node": ">=12.0.0" + } + }, + "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, + "engines": { + "node": ">= 4" + } + }, + "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==", + "dev": true, + "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, + "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, + "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 + }, + "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==", + "engines": { + "node": ">=12" + } + }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "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, + "engines": { + "node": ">=0.10.0" + } + }, + "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, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "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, + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "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==" + }, + "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==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "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 + }, + "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 + }, + "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 + }, + "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, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/klona": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz", + "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==", + "engines": { + "node": ">= 8" + } + }, + "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, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "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, + "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==" + }, + "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 + }, + "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==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", + "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", + "dev": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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 + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "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 + }, + "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==", + "engines": { + "node": ">=0.10.0" + } + }, + "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, + "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, + "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, + "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, + "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==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "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, + "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, + "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, + "engines": { + "node": ">=8" + } + }, + "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==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "dev": true, + "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" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-js": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", + "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", + "dev": true, + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-mixins": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/postcss-mixins/-/postcss-mixins-9.0.4.tgz", + "integrity": "sha512-XVq5jwQJDRu5M1XGkdpgASqLk37OqkH4JCFDXl/Dn7janOJjCTEKL+36cnRVy7bMtoBzALfO7bV7nTIsFnUWLA==", + "dev": true, + "dependencies": { + "fast-glob": "^3.2.11", + "postcss-js": "^4.0.0", + "postcss-simple-vars": "^7.0.0", + "sugarss": "^4.0.1" + }, + "engines": { + "node": ">=14.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-nested": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz", + "integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.11" + }, + "engines": { + "node": ">=12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-preset-mantine": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/postcss-preset-mantine/-/postcss-preset-mantine-1.15.0.tgz", + "integrity": "sha512-OKPs6uoORSXlU/GFH1ZtFaslecHBPwuoSikdL5W3WKJm4ZPAQM0mw9x9m3toa/Mo1JhoBmYMM28i+zEdav5Edg==", + "dev": true, + "dependencies": { + "postcss-mixins": "^9.0.4", + "postcss-nested": "^6.0.1" + }, + "peerDependencies": { + "postcss": ">=8.0.0" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.0.tgz", + "integrity": "sha512-UMz42UD0UY0EApS0ZL9o1XnLhSTtvvvLe5Dc2H2O56fvRZi+KulDyf5ctDhhtYJBGKStV2FL1fy6253cmLgqVQ==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-simple-vars": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-simple-vars/-/postcss-simple-vars-7.0.1.tgz", + "integrity": "sha512-5GLLXaS8qmzHMOjVxqkk1TZPf1jMqesiI7qLhnlyERalG0sMbHIbJqrcnrpmZdKCLglHnRHoEBB61RtGTsj++A==", + "dev": true, + "engines": { + "node": ">=14.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.2.1" + } + }, + "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, + "engines": { + "node": ">= 0.8.0" + } + }, + "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==", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "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, + "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" + } + ] + }, + "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==", + "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==", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "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==" + }, + "node_modules/react-number-format": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/react-number-format/-/react-number-format-5.4.0.tgz", + "integrity": "sha512-NWdICrqLhI7rAS8yUeLVd6Wr4cN7UjJ9IBTS0f/a9i7UB4x4Ti70kGnksBtZ7o4Z7YRbvCMMR/jQmkoOBa/4fg==", + "dependencies": { + "prop-types": "^15.7.2" + }, + "peerDependencies": { + "react": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/react-remove-scroll": { + "version": "2.5.10", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.10.tgz", + "integrity": "sha512-m3zvBRANPBw3qxVVjEIPEQinkcwlFZ4qyomuWVpNJdv4c6MvHfXV0C3L9Jx5rr3HeBHKNRX+1jreB5QloDIJjA==", + "dependencies": { + "react-remove-scroll-bar": "^2.3.6", + "react-style-singleton": "^2.2.1", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.0", + "use-sidecar": "^1.1.2" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.6.tgz", + "integrity": "sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==", + "dependencies": { + "react-style-singleton": "^2.2.1", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-router": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.24.1.tgz", + "integrity": "sha512-PTXFXGK2pyXpHzVo3rR9H7ip4lSPZZc0bHG5CARmj65fTT6qG7sTngmb6lcYu1gf3y/8KxORoy9yn59pGpCnpg==", + "dependencies": { + "@remix-run/router": "1.17.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.24.1.tgz", + "integrity": "sha512-U19KtXqooqw967Vw0Qcn5cOvrX5Ejo9ORmOtJMzYWtCT4/WOfFLIZGGsVLxcd9UkBO0mSTZtXqhZBsWlHr7+Sg==", + "dependencies": { + "@remix-run/router": "1.17.1", + "react-router": "6.24.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==", + "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-style-singleton": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", + "integrity": "sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==", + "dependencies": { + "get-nonce": "^1.0.0", + "invariant": "^2.2.4", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-textarea-autosize": { + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.5.3.tgz", + "integrity": "sha512-XT1024o2pqCuZSuBt9FwHlaDeNtVrtCXu0Rnz88t1jUGheCLa3PhjE1GH8Ctm2axEtvdCl5SUHYschyQ0L5QHQ==", + "dependencies": { + "@babel/runtime": "^7.20.13", + "use-composed-ref": "^1.3.0", + "use-latest": "^1.2.1" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "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==", + "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.12.7", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.12.7.tgz", + "integrity": "sha512-hlLJMhPQfv4/3NBSAyq3gzGg4h2v69RJh6KU7b3pXYNNAELs9kEoXOjbkxdXpALqKBoVmVptGfLpxdaVYqjmXQ==", + "dependencies": { + "clsx": "^2.0.0", + "eventemitter3": "^4.0.1", + "lodash": "^4.17.21", + "react-is": "^16.10.2", + "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==", + "dependencies": { + "decimal.js-light": "^2.4.1" + } + }, + "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==" + }, + "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==", + "dev": true, + "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, + "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, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.18.0.tgz", + "integrity": "sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg==", + "dev": true, + "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.18.0", + "@rollup/rollup-android-arm64": "4.18.0", + "@rollup/rollup-darwin-arm64": "4.18.0", + "@rollup/rollup-darwin-x64": "4.18.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.18.0", + "@rollup/rollup-linux-arm-musleabihf": "4.18.0", + "@rollup/rollup-linux-arm64-gnu": "4.18.0", + "@rollup/rollup-linux-arm64-musl": "4.18.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.18.0", + "@rollup/rollup-linux-riscv64-gnu": "4.18.0", + "@rollup/rollup-linux-s390x-gnu": "4.18.0", + "@rollup/rollup-linux-x64-gnu": "4.18.0", + "@rollup/rollup-linux-x64-musl": "4.18.0", + "@rollup/rollup-win32-arm64-msvc": "4.18.0", + "@rollup/rollup-win32-ia32-msvc": "4.18.0", + "@rollup/rollup-win32-x64-msvc": "4.18.0", + "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" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "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, + "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, + "engines": { + "node": ">=8" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "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, + "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, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/sugarss": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/sugarss/-/sugarss-4.0.1.tgz", + "integrity": "sha512-WCjS5NfuVJjkQzK10s8WOBY+hhDxxNt/N6ZaGwxFZ+wN3/lKKFSaaKUNecULcTTvE4urLcKaZFQD8vO0mOZujw==", + "dev": true, + "engines": { + "node": ">=12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.3.3" + } + }, + "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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tabbable": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", + "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==" + }, + "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 + }, + "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==" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-api-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", + "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "dev": true, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/tslib": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==" + }, + "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, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "4.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.19.0.tgz", + "integrity": "sha512-CN2l+hWACRiejlnr68vY0/7734Kzu+9+TOslUXbSCQ1ruY9XIHDBSceVXCcHm/oXrdzhtLMMdJEKfemf1yXiZQ==", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, + "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, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/use-callback-ref": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.2.tgz", + "integrity": "sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA==", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-composed-ref": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/use-composed-ref/-/use-composed-ref-1.3.0.tgz", + "integrity": "sha512-GLMG0Jc/jiKov/3Ulid1wbv3r54K9HlMW29IWcDFPEqFkSO2nS0MuefWgMJpeHQ9YJeXDL3ZUF+P3jdXlZX/cQ==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/use-isomorphic-layout-effect": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz", + "integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-latest": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/use-latest/-/use-latest-1.2.1.tgz", + "integrity": "sha512-xA+AVm/Wlg3e2P/JiItTziwS7FK92LWrDB0p+hgXloIMuVCeJJ8v6f0eeHyPZaJrM+usM1FkFfbNCrJGs8A/zw==", + "dependencies": { + "use-isomorphic-layout-effect": "^1.1.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz", + "integrity": "sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.9.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "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==", + "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.2.12", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.12.tgz", + "integrity": "sha512-/gC8GxzxMK5ntBwb48pR32GGhENnjtY30G4A0jemunsBkiEZFw60s8InGpN8gkhHEkjnRK1aSAxeQgwvFhUHAA==", + "dev": true, + "dependencies": { + "esbuild": "^0.20.1", + "postcss": "^8.4.38", + "rollup": "^4.13.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": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "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, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "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, + "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 + }, + "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, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json index 41e5eb2..9e91a2e 100644 --- a/package.json +++ b/package.json @@ -23,15 +23,17 @@ "dayjs": "^1.11.11", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-router-dom": "^6.23.1", + "react-router-dom": "^6.24.1", "recharts": "2" }, "devDependencies": { + "@types/node": "^20.14.9", "@types/react": "^18.2.66", "@types/react-dom": "^18.2.22", "@typescript-eslint/eslint-plugin": "^7.2.0", "@typescript-eslint/parser": "^7.2.0", "@vitejs/plugin-react-swc": "^3.5.0", + "dotenv": "^16.4.5", "eslint": "^8.57.0", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.6", diff --git a/src/App.css b/src/App.css deleted file mode 100644 index b9d355d..0000000 --- a/src/App.css +++ /dev/null @@ -1,42 +0,0 @@ -#root { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; -} - -.logo { - height: 6em; - padding: 1.5em; - will-change: filter; - transition: filter 300ms; -} -.logo:hover { - filter: drop-shadow(0 0 2em #646cffaa); -} -.logo.react:hover { - filter: drop-shadow(0 0 2em #61dafbaa); -} - -@keyframes logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} - -@media (prefers-reduced-motion: no-preference) { - a:nth-of-type(2) .logo { - animation: logo-spin infinite 20s linear; - } -} - -.card { - padding: 2em; -} - -.read-the-docs { - color: #888; -} diff --git a/src/App.tsx b/src/App.tsx index 69f1e6c..f1418c1 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,35 +1,23 @@ import "@mantine/core/styles.css"; import { MantineProvider } from "@mantine/core"; -import { useState } from "react"; -import reactLogo from "./assets/react.svg"; -import viteLogo from "/vite.svg"; -import "./App.css"; +import Home from "./pages/Home"; +import Login from "./pages/Login"; +import { BrowserRouter, Routes, Route } from "react-router-dom"; +import config from "./config"; +import { ConfigProvider } from "./ConfigContext"; -function App() { - const [count, setCount] = useState(0); +function App() { return ( -
- - Vite logo - - - React logo - -
-

Vite + React

-
- -

- Edit src/App.tsx and save to test HMR -

-
-

- Click on the Vite and React logos to learn more -

+ + + + } /> + } /> + + +
); } diff --git a/src/ConfigContext.tsx b/src/ConfigContext.tsx new file mode 100644 index 0000000..a73531e --- /dev/null +++ b/src/ConfigContext.tsx @@ -0,0 +1,26 @@ +// ConfigContext.tsx +import React, { createContext, useContext, ReactNode } from 'react'; +import AppConfig from './config'; + +interface ConfigContextType { + config: typeof AppConfig; +} + +const ConfigContext = createContext(undefined); + +export const useConfig = () => { + const context = useContext(ConfigContext); + if (!context) { + throw new Error('useConfig must be used within a ConfigProvider'); + } + return context; +}; + +interface ConfigProviderProps { + config: typeof AppConfig; + children: ReactNode; // Ensure children prop is defined +} + +export const ConfigProvider: React.FC = ({ config, children }) => { + return {children}; +}; diff --git a/src/assets/react.svg b/src/assets/react.svg deleted file mode 100644 index 6c87de9..0000000 --- a/src/assets/react.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 0000000..4557a58 --- /dev/null +++ b/src/config.ts @@ -0,0 +1,11 @@ +export interface AppConfig { + apiUrl: string; + // Add other configuration properties here if needed +} + +const config: AppConfig = { + apiUrl: import.meta.env.VITE_API_KEY as string, + // Define other properties as needed +}; + +export default config; diff --git a/src/index.css b/src/index.css index 6119ad9..e69de29 100644 --- a/src/index.css +++ b/src/index.css @@ -1,68 +0,0 @@ -:root { - font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; - line-height: 1.5; - font-weight: 400; - - color-scheme: light dark; - color: rgba(255, 255, 255, 0.87); - background-color: #242424; - - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; -} -a:hover { - color: #535bf2; -} - -body { - margin: 0; - display: flex; - place-items: center; - min-width: 320px; - min-height: 100vh; -} - -h1 { - font-size: 3.2em; - line-height: 1.1; -} - -button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - background-color: #1a1a1a; - cursor: pointer; - transition: border-color 0.25s; -} -button:hover { - border-color: #646cff; -} -button:focus, -button:focus-visible { - outline: 4px auto -webkit-focus-ring-color; -} - -@media (prefers-color-scheme: light) { - :root { - color: #213547; - background-color: #ffffff; - } - a:hover { - color: #747bff; - } - button { - background-color: #f9f9f9; - } -} diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx new file mode 100644 index 0000000..c886a58 --- /dev/null +++ b/src/pages/Home.tsx @@ -0,0 +1,18 @@ +import { Slider } from '@mantine/core'; + +const Home = () => { + return ( + <> +
Home
+ + + ) +} + +export default Home \ No newline at end of file diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx index e69de29..8cecfef 100644 --- a/src/pages/Login.tsx +++ b/src/pages/Login.tsx @@ -0,0 +1,138 @@ +import { TextInput, Button } from '@mantine/core'; +import { useEffect, useRef, useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { useConfig } from '../ConfigContext'; + + +async function getCSRFToken(): Promise { + try { + const response = await fetch('http://127.0.0.1:8000/api/v2/get_csrf/', { + method: 'GET', + headers: { + 'Content-Type': 'application/json' + }, + credentials: 'include' + }); + if (!response.ok) { + throw new Error('Failed to fetch CSRF token'); + } + const data = await response.json(); + return data.csrfToken; + } catch (error) { + console.error('Error fetching CSRF token:', error); + throw error; + } +} + +async function apiCall(url: string, method: string, body: Record | null, csrftoken: string): Promise { + const options: RequestInit = { + method: method, + headers: { + 'Content-Type': 'application/json', + 'X-CSRFToken': csrftoken + }, + credentials: 'include', + body: body ? JSON.stringify(body) : null + }; + + const response = await fetch(url, options); + if (!response.ok) { + throw new Error('Network response was not ok'); + } + return response.json(); +} + +const Home = (): JSX.Element => { + const hasFetchedToken = useRef(false); + const [csrftoken, setCsrfToken] = useState(''); + const navigate = useNavigate(); + const { config } = useConfig(); + console.log('Config:', config.apiUrl); + + useEffect(() => { + if (!hasFetchedToken.current) { + getCSRFToken().then(token => { + setCsrfToken(token); + hasFetchedToken.current = true; + }).catch(error => { + console.error('Failed to fetch initial CSRF token:', error); + }); + } + }, []); + + const handleLogin = async (username: string, password: string) => { + try { + const url = `${config.apiUrl}/login/`; + const data = await apiCall(url, 'POST', { username, password }, csrftoken); + console.log('Login successful:', data); + navigate('/'); + } catch (error) { + console.error('There was a problem with the fetch operation:', error); + } + }; + + const handleLogout = async () => { + try { + const url = `${config.apiUrl}/logout/`; + const data = await apiCall(url, 'POST', null, csrftoken); + console.log(data); + navigate('/login') + // Handle successful logout + } catch (error) { + console.error('There was a problem with the fetch operation:', error); + } + }; + + const handleWhoAmI = async () => { + try { + const url = `${config.apiUrl}/whoami/`; + const data = await apiCall(url, 'GET', null, csrftoken); + console.log(data); + // Handle successful response + } catch (error) { + console.error('There was a problem with the fetch operation:', error); + } + }; + + return ( + <> +
Home
+ + + + + + + ); +}; + +export default Home; diff --git a/tsconfig.json b/tsconfig.json index a7fc6fb..761deb7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,10 +2,13 @@ "compilerOptions": { "target": "ES2020", "useDefineForClassFields": true, - "lib": ["ES2020", "DOM", "DOM.Iterable"], + "lib": [ + "ES2020", + "DOM", + "DOM.Iterable" + ], "module": "ESNext", "skipLibCheck": true, - /* Bundler mode */ "moduleResolution": "bundler", "allowImportingTsExtensions": true, @@ -13,13 +16,18 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - /* Linting */ "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, "noFallthroughCasesInSwitch": true }, - "include": ["src"], - "references": [{ "path": "./tsconfig.node.json" }] -} + "include": [ + "src" + ], + "references": [ + { + "path": "./tsconfig.node.json" + } + ] +} \ No newline at end of file From 2ffe06a7169b72892f7e07446d3d316bbf8321e2 Mon Sep 17 00:00:00 2001 From: Treyson Date: Mon, 8 Jul 2024 09:48:34 -0600 Subject: [PATCH 03/96] setup, putting things in proper place --- src/ConfigContext.tsx | 3 +-- src/config.ts | 4 ++-- src/pages/Login.tsx | 16 +++++++++++++--- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/ConfigContext.tsx b/src/ConfigContext.tsx index a73531e..9d208a9 100644 --- a/src/ConfigContext.tsx +++ b/src/ConfigContext.tsx @@ -1,4 +1,3 @@ -// ConfigContext.tsx import React, { createContext, useContext, ReactNode } from 'react'; import AppConfig from './config'; @@ -18,7 +17,7 @@ export const useConfig = () => { interface ConfigProviderProps { config: typeof AppConfig; - children: ReactNode; // Ensure children prop is defined + children: ReactNode; } export const ConfigProvider: React.FC = ({ config, children }) => { diff --git a/src/config.ts b/src/config.ts index 4557a58..e04b39b 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,11 +1,11 @@ export interface AppConfig { apiUrl: string; - // Add other configuration properties here if needed + // Add others } const config: AppConfig = { apiUrl: import.meta.env.VITE_API_KEY as string, - // Define other properties as needed + //Add others }; export default config; diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx index 8cecfef..08162c8 100644 --- a/src/pages/Login.tsx +++ b/src/pages/Login.tsx @@ -43,11 +43,14 @@ async function apiCall(url: string, method: string, body: Record | } const Home = (): JSX.Element => { + const [username, setUsername] = useState(''); + const [password, setPassword] = useState(''); + const hasFetchedToken = useRef(false); const [csrftoken, setCsrfToken] = useState(''); + const navigate = useNavigate(); const { config } = useConfig(); - console.log('Config:', config.apiUrl); useEffect(() => { if (!hasFetchedToken.current) { @@ -97,18 +100,25 @@ const Home = (): JSX.Element => { return ( <>
Home
- + setUsername(event.currentTarget.value)} + /> setPassword(event.currentTarget.value)} /> + + ); +}; + +export default LoginForm; diff --git a/src/components/Home/Home.tsx b/src/components/Home/Home.tsx new file mode 100644 index 0000000..545678f --- /dev/null +++ b/src/components/Home/Home.tsx @@ -0,0 +1,70 @@ +import { useState, useEffect } from 'react'; +import { Button } from '@mantine/core'; +import { useNavigate } from 'react-router-dom'; +import { useConfig } from '../../contexts/ConfigContext'; +import { apiCall } from '../../api'; +import { useCsrfToken } from '../../hooks/useCsrfToken'; + +const Home = (): JSX.Element => { + const navigate = useNavigate(); + const { config } = useConfig(); + const csrftoken = useCsrfToken(); + const [userName, setUserName] = useState(null); + + const handleLogout = async () => { + try { + const url = `${config.apiUrl}/logout/`; + const data = await apiCall(url, 'POST', null, csrftoken); + console.log(data); + navigate('/login') + } catch (error) { + console.error('There was a problem with the fetch operation:', error); + } + }; + + const handleWhoAmI = async () => { + try { + const url = `${config.apiUrl}/whoami/`; + const data = await apiCall(url, 'GET', null, csrftoken); + if (data.user === null) { + navigate('/login') + return; + } + console.log("Who Am I", data.user.username) + setUserName(data.user.username); + } catch (error) { + console.error('There was a problem with the fetch operation:', error); + } + }; + + useEffect(() => { + handleWhoAmI(); + }, []); + + return ( + <> +
Welcome Home
+ {userName &&
Hello, {userName}!
} + + + + ); +}; + +export default Home; diff --git a/src/config.ts b/src/config.ts index e04b39b..bf55bb8 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,11 +1,34 @@ export interface AppConfig { - apiUrl: string; - // Add others + apiUrl: HostName; + frontendUrl: HostName; } +type HostName = string & { readonly brand: unique symbol }; + +const isHostName = (url: string): url is HostName => { + try { + const parsedUrl = new URL(url); + const isHttps = parsedUrl.protocol === 'https:'; + const isLocalhost = parsedUrl.protocol === 'http:' && parsedUrl.hostname === '127.0.0.1' || parsedUrl.hostname === 'localhost' && import.meta.env.VITE_DEVELOPMENT === 'true'; + return (isHttps || isLocalhost) && Boolean(parsedUrl.hostname); + } catch { + return false; + } +}; + +const validateHostName = (url: string): HostName => { + if (isHostName(url)) { + return url; + } + throw new Error(`Invalid host name: ${url}`); +}; + +const apiUrl = validateHostName(import.meta.env.VITE_API_URL as string); +const frontendUrl = validateHostName(import.meta.env.VITE_FRONTEND_URL as string); + const config: AppConfig = { - apiUrl: import.meta.env.VITE_API_KEY as string, - //Add others + apiUrl, + frontendUrl, }; export default config; diff --git a/src/ConfigContext.tsx b/src/contexts/ConfigContext.tsx similarity index 95% rename from src/ConfigContext.tsx rename to src/contexts/ConfigContext.tsx index 9d208a9..3cc5301 100644 --- a/src/ConfigContext.tsx +++ b/src/contexts/ConfigContext.tsx @@ -1,5 +1,5 @@ import React, { createContext, useContext, ReactNode } from 'react'; -import AppConfig from './config'; +import AppConfig from '../config'; interface ConfigContextType { config: typeof AppConfig; diff --git a/src/hooks/useCsrfToken.ts b/src/hooks/useCsrfToken.ts new file mode 100644 index 0000000..ffb12a0 --- /dev/null +++ b/src/hooks/useCsrfToken.ts @@ -0,0 +1,20 @@ +import { useEffect, useRef, useState } from 'react'; +import { getCSRFToken } from '../api'; + +export const useCsrfToken = () => { + const [csrftoken, setCsrfToken] = useState(''); + const hasFetchedToken = useRef(false); + + useEffect(() => { + if (!hasFetchedToken.current) { + getCSRFToken().then(token => { + setCsrfToken(token); + hasFetchedToken.current = true; + }).catch(error => { + console.error('Failed to fetch initial CSRF token:', error); + }); + } + }, []); + + return csrftoken; +}; diff --git a/src/main.tsx b/src/main.tsx index 3d7150d..e63eef4 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,7 +1,6 @@ import React from 'react' import ReactDOM from 'react-dom/client' import App from './App.tsx' -import './index.css' ReactDOM.createRoot(document.getElementById('root')!).render( diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx deleted file mode 100644 index c886a58..0000000 --- a/src/pages/Home.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { Slider } from '@mantine/core'; - -const Home = () => { - return ( - <> -
Home
- - - ) -} - -export default Home \ No newline at end of file diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx deleted file mode 100644 index 08162c8..0000000 --- a/src/pages/Login.tsx +++ /dev/null @@ -1,148 +0,0 @@ -import { TextInput, Button } from '@mantine/core'; -import { useEffect, useRef, useState } from 'react'; -import { useNavigate } from 'react-router-dom'; -import { useConfig } from '../ConfigContext'; - - -async function getCSRFToken(): Promise { - try { - const response = await fetch('http://127.0.0.1:8000/api/v2/get_csrf/', { - method: 'GET', - headers: { - 'Content-Type': 'application/json' - }, - credentials: 'include' - }); - if (!response.ok) { - throw new Error('Failed to fetch CSRF token'); - } - const data = await response.json(); - return data.csrfToken; - } catch (error) { - console.error('Error fetching CSRF token:', error); - throw error; - } -} - -async function apiCall(url: string, method: string, body: Record | null, csrftoken: string): Promise { - const options: RequestInit = { - method: method, - headers: { - 'Content-Type': 'application/json', - 'X-CSRFToken': csrftoken - }, - credentials: 'include', - body: body ? JSON.stringify(body) : null - }; - - const response = await fetch(url, options); - if (!response.ok) { - throw new Error('Network response was not ok'); - } - return response.json(); -} - -const Home = (): JSX.Element => { - const [username, setUsername] = useState(''); - const [password, setPassword] = useState(''); - - const hasFetchedToken = useRef(false); - const [csrftoken, setCsrfToken] = useState(''); - - const navigate = useNavigate(); - const { config } = useConfig(); - - useEffect(() => { - if (!hasFetchedToken.current) { - getCSRFToken().then(token => { - setCsrfToken(token); - hasFetchedToken.current = true; - }).catch(error => { - console.error('Failed to fetch initial CSRF token:', error); - }); - } - }, []); - - const handleLogin = async (username: string, password: string) => { - try { - const url = `${config.apiUrl}/login/`; - const data = await apiCall(url, 'POST', { username, password }, csrftoken); - console.log('Login successful:', data); - navigate('/'); - } catch (error) { - console.error('There was a problem with the fetch operation:', error); - } - }; - - const handleLogout = async () => { - try { - const url = `${config.apiUrl}/logout/`; - const data = await apiCall(url, 'POST', null, csrftoken); - console.log(data); - navigate('/login') - // Handle successful logout - } catch (error) { - console.error('There was a problem with the fetch operation:', error); - } - }; - - const handleWhoAmI = async () => { - try { - const url = `${config.apiUrl}/whoami/`; - const data = await apiCall(url, 'GET', null, csrftoken); - console.log(data); - // Handle successful response - } catch (error) { - console.error('There was a problem with the fetch operation:', error); - } - }; - - return ( - <> -
Home
- setUsername(event.currentTarget.value)} - /> - setPassword(event.currentTarget.value)} - /> - - - - - ); -}; - -export default Home; diff --git a/src/index.css b/src/styles/index.css similarity index 100% rename from src/index.css rename to src/styles/index.css From 702505278d231873dca455afa3978660646e93f4 Mon Sep 17 00:00:00 2001 From: Treyson Date: Mon, 8 Jul 2024 11:56:42 -0600 Subject: [PATCH 05/96] README for demo, example env as well --- README.md | 47 +++++++++++++++++++++++------------------------ example.env | 2 +- 2 files changed, 24 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 62c8942..ce1c25d 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,29 @@ -# OpenIPAM React Frontend +# OpenIPAM Frontend Login DEMO +This is a locally hosted demo that shows the setup needed to allow for login through the seperate backend, dealing with CSRF and CORS errors -This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. +## Setup -Currently, two official plugins are available: +- First, copy `example.env` into `.env` +- You will fill this out in a moment -- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh -- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh +### OpenIPAM Backend +- You will need this cloned [GitHub Repo](https://github.com/Treyson-Grange/django-openipam) +- After cloning, use poetry to install packages, and run it. +- Note what host and port it is running on, and throw it in your `.env` +- If you work here you should have a dev db for OpenIPAM lol im not gonna help you there -## Expanding the ESLint configuration - -If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: - -- Configure the top-level `parserOptions` property like this: - -```js -export default { - // other rules... - parserOptions: { - ecmaVersion: 'latest', - sourceType: 'module', - project: ['./tsconfig.json', './tsconfig.node.json'], - tsconfigRootDir: __dirname, - }, -} +### OpenIPAM Frontend +- Inside of this repository, run the following commands ``` +npm install +npm run dev +``` +- Note the host and port, and throw it in your `.env` (You don't actually need this one) + +### Finally, +- Head to the frontend URL. +- Login with a username, specific to your local OpenIPAM db. -- Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked` -- Optionally add `plugin:@typescript-eslint/stylistic-type-checked` -- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list +## Todo For Demo +- [ ] Error Catching / Incorrect login +- [ ] Make it pretty \ No newline at end of file diff --git a/example.env b/example.env index fba04e2..0a736a2 100644 --- a/example.env +++ b/example.env @@ -1,5 +1,5 @@ VITE_API_URL=http://127.0.0.1:8000/api/v2 VITE_FRONTEND_URL=http://localhost:5173 -# Allows for use of local host 127.0.0.1 in development +# Allows for use of local host and 127.0.0.1 in development VITE_DEVELOPMENT=true \ No newline at end of file From 60d21520292a94bc8c96e347465891c0dcde3701 Mon Sep 17 00:00:00 2001 From: Treyson Date: Mon, 8 Jul 2024 12:19:33 -0600 Subject: [PATCH 06/96] Basic error handling, handles 401 and 400 from api --- README.md | 2 +- src/api.ts | 2 +- src/components/Auth/Login.tsx | 12 +++++++++--- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index ce1c25d..952de38 100644 --- a/README.md +++ b/README.md @@ -25,5 +25,5 @@ npm run dev - Login with a username, specific to your local OpenIPAM db. ## Todo For Demo -- [ ] Error Catching / Incorrect login +- [x] Error Catching / Incorrect login - [ ] Make it pretty \ No newline at end of file diff --git a/src/api.ts b/src/api.ts index 1c0533c..e6346b1 100644 --- a/src/api.ts +++ b/src/api.ts @@ -31,7 +31,7 @@ export async function apiCall(url: string, method: string, body: Record { const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); + const [error, setError] = useState(''); const navigate = useNavigate(); const { config } = useConfig(); @@ -20,8 +21,12 @@ const LoginForm = () => { const data = await apiCall(url, 'POST', { username, password }, csrftoken); console.log('Login successful:', data); navigate('/'); - } catch (error) { - console.error('There was a problem with the fetch operation:', error); + } catch (error: any) { + if (error.message === '401') { + setError('Invalid credentials'); + } else if (error.message === '400') { + setError('Bad request'); + } } }; @@ -41,6 +46,7 @@ const LoginForm = () => { value={password} onChange={(event) => setPassword(event.currentTarget.value)} /> + {error && {error}} - + + + Login +
{ e.preventDefault(); handleLogin(); }}> + + setUsername(event.currentTarget.value)} + required + size="lg" + /> + setPassword(event.currentTarget.value)} + required + size="lg" + /> + {error && {error}} + + +
+
+
); }; diff --git a/src/components/Home/Home.tsx b/src/components/Home/Home.tsx index 545678f..ebafa89 100644 --- a/src/components/Home/Home.tsx +++ b/src/components/Home/Home.tsx @@ -1,5 +1,5 @@ import { useState, useEffect } from 'react'; -import { Button } from '@mantine/core'; +import { TextInput, Button, Text, Group, Container, Paper, Title } from '@mantine/core'; import { useNavigate } from 'react-router-dom'; import { useConfig } from '../../contexts/ConfigContext'; import { apiCall } from '../../api'; @@ -43,27 +43,24 @@ const Home = (): JSX.Element => { return ( <> -
Welcome Home
- {userName &&
Hello, {userName}!
} - - + + + Home + Welcome, {userName} + + + + + + ); }; From 7f06ae1f30fe390fa7f96986f7269f4a703ec99d Mon Sep 17 00:00:00 2001 From: Treyson Date: Tue, 9 Jul 2024 09:42:12 -0600 Subject: [PATCH 08/96] Bout to dive into Ezris robust API system --- package-lock.json | 16 + package.json | 2 + src/App.tsx | 4 +- src/api.ts | 4 +- src/components/{Home => }/Home.tsx | 9 +- src/components/{Auth => }/Login.tsx | 6 +- src/hooks/useCsrfToken.ts | 5 +- src/hooks/useToken.ts | 5 + src/utilities/apiFunctions.ts | 46 +++ yarn.lock | 504 +++++++++------------------- 10 files changed, 243 insertions(+), 358 deletions(-) rename src/components/{Home => }/Home.tsx (86%) rename src/components/{Auth => }/Login.tsx (94%) create mode 100644 src/hooks/useToken.ts create mode 100644 src/utilities/apiFunctions.ts diff --git a/package-lock.json b/package-lock.json index 32a1661..4faa811 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,12 +19,14 @@ "@mantine/nprogress": "^7.10.1", "@mantine/spotlight": "^7.10.1", "dayjs": "^1.11.11", + "js-cookie": "^3.0.5", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.24.1", "recharts": "2" }, "devDependencies": { + "@types/js-cookie": "^3.0.6", "@types/node": "^20.14.9", "@types/react": "^18.2.66", "@types/react-dom": "^18.2.22", @@ -1268,6 +1270,12 @@ "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", "dev": true }, + "node_modules/@types/js-cookie": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.6.tgz", + "integrity": "sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==", + "dev": true + }, "node_modules/@types/node": { "version": "20.14.9", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.9.tgz", @@ -2525,6 +2533,14 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, + "node_modules/js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "engines": { + "node": ">=14" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", diff --git a/package.json b/package.json index 9e91a2e..41af234 100644 --- a/package.json +++ b/package.json @@ -21,12 +21,14 @@ "@mantine/nprogress": "^7.10.1", "@mantine/spotlight": "^7.10.1", "dayjs": "^1.11.11", + "js-cookie": "^3.0.5", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.24.1", "recharts": "2" }, "devDependencies": { + "@types/js-cookie": "^3.0.6", "@types/node": "^20.14.9", "@types/react": "^18.2.66", "@types/react-dom": "^18.2.22", diff --git a/src/App.tsx b/src/App.tsx index 961a3e2..5ea208e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,7 +1,7 @@ import "@mantine/core/styles.css"; import { MantineProvider } from "@mantine/core"; -import Home from "./components/Home/Home"; -import Login from "./components/Auth/Login"; +import Home from "./components/Home"; +import Login from "./components/Login"; import { BrowserRouter, Routes, Route } from "react-router-dom"; import config from "./config"; import { ConfigProvider } from "./contexts/ConfigContext"; diff --git a/src/api.ts b/src/api.ts index e6346b1..7e4d02c 100644 --- a/src/api.ts +++ b/src/api.ts @@ -1,6 +1,6 @@ -export async function getCSRFToken(): Promise { +export async function getCSRFToken(apiUrl: string): Promise { try { - const response = await fetch('http://127.0.0.1:8000/api/v2/get_csrf/', { + const response = await fetch(`${apiUrl}/get_csrf/`, { method: 'GET', headers: { 'Content-Type': 'application/json' diff --git a/src/components/Home/Home.tsx b/src/components/Home.tsx similarity index 86% rename from src/components/Home/Home.tsx rename to src/components/Home.tsx index ebafa89..cf1822c 100644 --- a/src/components/Home/Home.tsx +++ b/src/components/Home.tsx @@ -1,9 +1,9 @@ import { useState, useEffect } from 'react'; -import { TextInput, Button, Text, Group, Container, Paper, Title } from '@mantine/core'; +import { Button, Text, Group, Container, Paper, Title } from '@mantine/core'; import { useNavigate } from 'react-router-dom'; -import { useConfig } from '../../contexts/ConfigContext'; -import { apiCall } from '../../api'; -import { useCsrfToken } from '../../hooks/useCsrfToken'; +import { useConfig } from '../contexts/ConfigContext'; +import { apiCall } from '../api'; +import { useCsrfToken } from '../hooks/useCsrfToken'; const Home = (): JSX.Element => { const navigate = useNavigate(); @@ -30,7 +30,6 @@ const Home = (): JSX.Element => { navigate('/login') return; } - console.log("Who Am I", data.user.username) setUserName(data.user.username); } catch (error) { console.error('There was a problem with the fetch operation:', error); diff --git a/src/components/Auth/Login.tsx b/src/components/Login.tsx similarity index 94% rename from src/components/Auth/Login.tsx rename to src/components/Login.tsx index 6e077f8..8f783c7 100644 --- a/src/components/Auth/Login.tsx +++ b/src/components/Login.tsx @@ -1,9 +1,9 @@ import { TextInput, Button, Text, Group, Container, Paper, Title } from '@mantine/core'; import { useState } from 'react'; import { useNavigate } from 'react-router-dom'; -import { useConfig } from '../../contexts/ConfigContext'; -import { apiCall } from '../../api'; -import { useCsrfToken } from '../../hooks/useCsrfToken'; +import { useConfig } from '../contexts/ConfigContext'; +import { apiCall } from '../api'; +import { useCsrfToken } from '../hooks/useCsrfToken'; const LoginForm = () => { const [username, setUsername] = useState(''); diff --git a/src/hooks/useCsrfToken.ts b/src/hooks/useCsrfToken.ts index ffb12a0..dea9ce0 100644 --- a/src/hooks/useCsrfToken.ts +++ b/src/hooks/useCsrfToken.ts @@ -1,13 +1,14 @@ import { useEffect, useRef, useState } from 'react'; import { getCSRFToken } from '../api'; +import { useConfig } from '../contexts/ConfigContext'; export const useCsrfToken = () => { const [csrftoken, setCsrfToken] = useState(''); const hasFetchedToken = useRef(false); - + const { config } = useConfig(); useEffect(() => { if (!hasFetchedToken.current) { - getCSRFToken().then(token => { + getCSRFToken(config.apiUrl).then(token => { setCsrfToken(token); hasFetchedToken.current = true; }).catch(error => { diff --git a/src/hooks/useToken.ts b/src/hooks/useToken.ts new file mode 100644 index 0000000..ed10efc --- /dev/null +++ b/src/hooks/useToken.ts @@ -0,0 +1,5 @@ +import Cookies from "js-cookie"; + +export const useToken = () => { + return Cookies.get("csrftoken"); +}; \ No newline at end of file diff --git a/src/utilities/apiFunctions.ts b/src/utilities/apiFunctions.ts new file mode 100644 index 0000000..dd61c2d --- /dev/null +++ b/src/utilities/apiFunctions.ts @@ -0,0 +1,46 @@ +import { useToken } from "../hooks/useToken"; + +export const getApiEndpointFunctions = () => ({ + /** + * Logs API + */ + logs: { + get: requestGenerator(HttpMethod.GET, "logs"), + getEmails: requestGenerator(HttpMethod.GET, "logs/email"), + }, +}) + +const BASE_URL = import.meta.env.VITE_API_URL + +enum HttpMethod { + GET = "GET", + POST = "POST", + PUT = "PUT", + DELETE = "DELETE", + PATCH = "PATCH", +} + +function requestGenerator(method: string, url: string, base?: string) { + url = `${base ?? BASE_URL}/${url}`; + const token = useToken(); + switch (method) { + case "GET": + return async (params?: { [key: string]: any }) => { + const query = new URLSearchParams(params ?? {}).toString(); + const response = await fetch(`${url}?${query}`); + return response.json(); + }; + default: + return async (data?: { [key: string]: any }) => { + const response = await fetch(url, { + method, + headers: { + "Content-Type": "application/json", + "X-CSRFToken": token ?? "", + }, + body: JSON.stringify(data ?? {}), + }); + return response.json(); + }; + } +} diff --git a/yarn.lock b/yarn.lock index 89f83ca..e149fff 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4,126 +4,16 @@ "@babel/runtime@^7.20.13", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7": version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.7.tgz#f4f0d5530e8dbdf59b3451b9b3e594b6ba082e12" + resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.7.tgz" integrity sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw== dependencies: regenerator-runtime "^0.14.0" -"@esbuild/aix-ppc64@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz#a70f4ac11c6a1dfc18b8bbb13284155d933b9537" - integrity sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g== - -"@esbuild/android-arm64@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz#db1c9202a5bc92ea04c7b6840f1bbe09ebf9e6b9" - integrity sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg== - -"@esbuild/android-arm@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.20.2.tgz#3b488c49aee9d491c2c8f98a909b785870d6e995" - integrity sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w== - -"@esbuild/android-x64@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.20.2.tgz#3b1628029e5576249d2b2d766696e50768449f98" - integrity sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg== - -"@esbuild/darwin-arm64@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz#6e8517a045ddd86ae30c6608c8475ebc0c4000bb" - integrity sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA== - -"@esbuild/darwin-x64@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz#90ed098e1f9dd8a9381695b207e1cff45540a0d0" - integrity sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA== - -"@esbuild/freebsd-arm64@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz#d71502d1ee89a1130327e890364666c760a2a911" - integrity sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw== - -"@esbuild/freebsd-x64@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz#aa5ea58d9c1dd9af688b8b6f63ef0d3d60cea53c" - integrity sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw== - -"@esbuild/linux-arm64@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz#055b63725df678379b0f6db9d0fa85463755b2e5" - integrity sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A== - -"@esbuild/linux-arm@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz#76b3b98cb1f87936fbc37f073efabad49dcd889c" - integrity sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg== - -"@esbuild/linux-ia32@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz#c0e5e787c285264e5dfc7a79f04b8b4eefdad7fa" - integrity sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig== - -"@esbuild/linux-loong64@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz#a6184e62bd7cdc63e0c0448b83801001653219c5" - integrity sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ== - -"@esbuild/linux-mips64el@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz#d08e39ce86f45ef8fc88549d29c62b8acf5649aa" - integrity sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA== - -"@esbuild/linux-ppc64@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz#8d252f0b7756ffd6d1cbde5ea67ff8fd20437f20" - integrity sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg== - -"@esbuild/linux-riscv64@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz#19f6dcdb14409dae607f66ca1181dd4e9db81300" - integrity sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg== - -"@esbuild/linux-s390x@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz#3c830c90f1a5d7dd1473d5595ea4ebb920988685" - integrity sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ== - "@esbuild/linux-x64@0.20.2": version "0.20.2" resolved "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz" integrity sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw== -"@esbuild/netbsd-x64@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz#e771c8eb0e0f6e1877ffd4220036b98aed5915e6" - integrity sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ== - -"@esbuild/openbsd-x64@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz#9a795ae4b4e37e674f0f4d716f3e226dd7c39baf" - integrity sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ== - -"@esbuild/sunos-x64@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz#7df23b61a497b8ac189def6e25a95673caedb03f" - integrity sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w== - -"@esbuild/win32-arm64@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz#f1ae5abf9ca052ae11c1bc806fb4c0f519bacf90" - integrity sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ== - -"@esbuild/win32-ia32@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz#241fe62c34d8e8461cd708277813e1d0ba55ce23" - integrity sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ== - -"@esbuild/win32-x64@0.20.2": - version "0.20.2" - resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz#9c907b21e30a52db959ba4f80bb01a0cc403d5cc" - integrity sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ== - "@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": version "4.4.0" resolved "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz" @@ -158,14 +48,14 @@ "@floating-ui/core@^1.0.0": version "1.6.2" - resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.6.2.tgz#d37f3e0ac1f1c756c7de45db13303a266226851a" + resolved "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.2.tgz" integrity sha512-+2XpQV9LLZeanU4ZevzRnGFg2neDeKHgFLjP6YLW+tly0IvrhqT4u8enLGjLH3qeh85g19xY5rsAusfwTdn5lg== dependencies: "@floating-ui/utils" "^0.2.0" "@floating-ui/dom@^1.0.0": version "1.6.5" - resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.5.tgz#323f065c003f1d3ecf0ff16d2c2c4d38979f4cb9" + resolved "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.5.tgz" integrity sha512-Nsdud2X65Dz+1RHjAIP0t8z5e2ff/IRbei6BqFrl1urT8sDVzM1HMQ+R0XcU5ceRfyO3I6ayeqIfh+6Wb8LGTw== dependencies: "@floating-ui/core" "^1.0.0" @@ -173,14 +63,14 @@ "@floating-ui/react-dom@^2.1.0": version "2.1.0" - resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.1.0.tgz#4f0e5e9920137874b2405f7d6c862873baf4beff" + resolved "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.0.tgz" integrity sha512-lNzj5EQmEKn5FFKc04+zasr09h/uX8RtJRNj5gUXsSQIXHVWTVh+hVAg1vOMCexkX8EgvemMvIFpQfkosnVNyA== dependencies: "@floating-ui/dom" "^1.0.0" "@floating-ui/react@^0.26.9": version "0.26.16" - resolved "https://registry.yarnpkg.com/@floating-ui/react/-/react-0.26.16.tgz#3415a087f452165161c2d313d1d57e8142894679" + resolved "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.16.tgz" integrity sha512-HEf43zxZNAI/E781QIVpYSF3K2VH4TTYZpqecjdsFkjsaU1EbaWcM++kw0HXFffj7gDUcBFevX8s0rQGQpxkow== dependencies: "@floating-ui/react-dom" "^2.1.0" @@ -189,7 +79,7 @@ "@floating-ui/utils@^0.2.0": version "0.2.2" - resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.2.tgz#d8bae93ac8b815b2bd7a98078cf91e2724ef11e5" + resolved "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.2.tgz" integrity sha512-J4yDIIthosAsRZ5CPYP/jQvUAQtlZTTD/4suA08/FEnlxqW3sKS9iAhgsa9VYLZ6vDHn/ixJgIqRQPotoBjxIw== "@humanwhocodes/config-array@^0.11.14": @@ -213,20 +103,20 @@ "@mantine/charts@^7.10.1": version "7.10.1" - resolved "https://registry.yarnpkg.com/@mantine/charts/-/charts-7.10.1.tgz#c1b92ccac16d05b87cc103adcd79f0e0c2f87c62" + resolved "https://registry.npmjs.org/@mantine/charts/-/charts-7.10.1.tgz" integrity sha512-fMy2EmgegdHVkrtnRO8ync8kqOJoXqixxc1JDmhn9tks4lSvKAKB9ui8OyaTUOWzls/Cs0VfyW51sB/HKe5faw== "@mantine/code-highlight@^7.10.1": version "7.10.1" - resolved "https://registry.yarnpkg.com/@mantine/code-highlight/-/code-highlight-7.10.1.tgz#96f90c5c142d00be64c4177dab9ae6803def187f" + resolved "https://registry.npmjs.org/@mantine/code-highlight/-/code-highlight-7.10.1.tgz" integrity sha512-ZeqBnd/i6CNF8avmjgYNqo9hKFnrzYoKV13OrKAHNRZk7vdbBGoSeVYF1vq+ChDBOZQfLOW+2naTi3VdzwLOhg== dependencies: clsx "^2.1.1" highlight.js "^11.9.0" -"@mantine/core@^7.10.1": +"@mantine/core@^7.10.1", "@mantine/core@7.10.1": version "7.10.1" - resolved "https://registry.yarnpkg.com/@mantine/core/-/core-7.10.1.tgz#c90a2aed1e47ece38f745db11c1cff56fd5719b9" + resolved "https://registry.npmjs.org/@mantine/core/-/core-7.10.1.tgz" integrity sha512-l9ypojKN3PjwO1CSLIsqxi7mA25+7w+xc71Q+JuCCREI0tuGwkZsKbIOpuTATIJOjPh8ycLiW7QxX1LYsRTq6w== dependencies: "@floating-ui/react" "^0.26.9" @@ -238,32 +128,32 @@ "@mantine/dates@^7.10.1": version "7.10.1" - resolved "https://registry.yarnpkg.com/@mantine/dates/-/dates-7.10.1.tgz#2cbc0a69e713b4046a83662cfdbc40784c5bbaa4" + resolved "https://registry.npmjs.org/@mantine/dates/-/dates-7.10.1.tgz" integrity sha512-XkzYaHgzJPrquG78/exJd0dLeghJvRgypfSRwFH7IcR34Eo2MD3nsuorZp09i9sYnROcXqIFhZWgwk5cqg/8nw== dependencies: clsx "^2.1.1" "@mantine/form@^7.10.1": version "7.10.1" - resolved "https://registry.yarnpkg.com/@mantine/form/-/form-7.10.1.tgz#86b867454b7490f4ca58c924d85d4170520cec4d" + resolved "https://registry.npmjs.org/@mantine/form/-/form-7.10.1.tgz" integrity sha512-mZwzg4GEWKEDKEIZu9FmSpGFzYYhFD2YArVOXUM0MMciUqX7yxSCon1PaPJxrV8ldc6FE+JLVI2+G2KVxJ3ZXA== dependencies: fast-deep-equal "^3.1.3" klona "^2.0.6" -"@mantine/hooks@^7.10.1": +"@mantine/hooks@^7.10.1", "@mantine/hooks@7.10.1": version "7.10.1" - resolved "https://registry.yarnpkg.com/@mantine/hooks/-/hooks-7.10.1.tgz#68d12570f0dad127555904973ec78ae40065ae31" + resolved "https://registry.npmjs.org/@mantine/hooks/-/hooks-7.10.1.tgz" integrity sha512-0EH9WBWUdtQLGU3Ak+csQ77EtUxI6pPNfwZdRJQWcaA3f8SFOLo9h9CGxiikFExerhvuCeUlaTf3s+TB9Op/rw== "@mantine/modals@^7.10.1": version "7.10.1" - resolved "https://registry.yarnpkg.com/@mantine/modals/-/modals-7.10.1.tgz#28cab8bdf432ac185b33fb3bcaeae93bc4e85747" + resolved "https://registry.npmjs.org/@mantine/modals/-/modals-7.10.1.tgz" integrity sha512-2riQSNpVV7f0baizlqcggz9hx9/+y6SQTnW3zEkl/RIkuyK9dpeMFUG6M+M8ntwP79b7x9n7Em9PMWxRbgi28A== "@mantine/notifications@^7.10.1": version "7.10.1" - resolved "https://registry.yarnpkg.com/@mantine/notifications/-/notifications-7.10.1.tgz#05622ec6966a8eaa77cf4e5c65ec9cf2ad0f5bcd" + resolved "https://registry.npmjs.org/@mantine/notifications/-/notifications-7.10.1.tgz" integrity sha512-cx3JR3BJzEzH6t2EF1ysrWVY/rdJk0WbSBQo/qFamJd2sbU+8XAHriI8Cx6hNo7uRGCwd8VGAj7Cf3aWK2VC5A== dependencies: "@mantine/store" "7.10.1" @@ -271,21 +161,21 @@ "@mantine/nprogress@^7.10.1": version "7.10.1" - resolved "https://registry.yarnpkg.com/@mantine/nprogress/-/nprogress-7.10.1.tgz#d79a2ca73f6d7d6aa01423fdf8a3e16c87385ea9" + resolved "https://registry.npmjs.org/@mantine/nprogress/-/nprogress-7.10.1.tgz" integrity sha512-irzVf35N8YQJEM5TwlzF0lZeguHeMejN005IMFg2UvlMw8ba+WiXGlK4vELhCq0+37yDqRsxl8NKf6crQiNVRQ== dependencies: "@mantine/store" "7.10.1" "@mantine/spotlight@^7.10.1": version "7.10.1" - resolved "https://registry.yarnpkg.com/@mantine/spotlight/-/spotlight-7.10.1.tgz#469ce55bd9b5501083d0901d93aaec95fa4004b6" + resolved "https://registry.npmjs.org/@mantine/spotlight/-/spotlight-7.10.1.tgz" integrity sha512-0G9HLaSbgJvSZRFIsEPyLUPM9heLBsYsKNYAeh6gTE6Q0/rdc5WrXxPUyOzdUaZVu4JVPvdzx/Ov5fa+kGgU4g== dependencies: "@mantine/store" "7.10.1" "@mantine/store@7.10.1": version "7.10.1" - resolved "https://registry.yarnpkg.com/@mantine/store/-/store-7.10.1.tgz#b8201cb0b76bd3feaeecc3818155f06d967e46f4" + resolved "https://registry.npmjs.org/@mantine/store/-/store-7.10.1.tgz" integrity sha512-KrGBsSoMsfrYeLxPwf5rFv0s2Nl/4wf+AaF/U1SpQrMgPI8vYokPXx52Wp3jCmlo12NCZnCIG+/6YHAdTWH1qQ== "@nodelib/fs.scandir@2.1.5": @@ -296,7 +186,7 @@ "@nodelib/fs.stat" "2.0.5" run-parallel "^1.1.9" -"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": +"@nodelib/fs.stat@^2.0.2", "@nodelib/fs.stat@2.0.5": version "2.0.5" resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz" integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== @@ -309,65 +199,10 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@remix-run/router@1.16.1": - version "1.16.1" - resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.16.1.tgz#73db3c48b975eeb06d0006481bde4f5f2d17d1cd" - integrity sha512-es2g3dq6Nb07iFxGk5GuHN20RwBZOsuDQN7izWIisUcv9r+d2C5jQxqmgkdebXgReWfiyUabcki6Fg77mSNrig== - -"@rollup/rollup-android-arm-eabi@4.18.0": - version "4.18.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.0.tgz#bbd0e616b2078cd2d68afc9824d1fadb2f2ffd27" - integrity sha512-Tya6xypR10giZV1XzxmH5wr25VcZSncG0pZIjfePT0OVBvqNEurzValetGNarVrGiq66EBVAFn15iYX4w6FKgQ== - -"@rollup/rollup-android-arm64@4.18.0": - version "4.18.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.18.0.tgz#97255ef6384c5f73f4800c0de91f5f6518e21203" - integrity sha512-avCea0RAP03lTsDhEyfy+hpfr85KfyTctMADqHVhLAF3MlIkq83CP8UfAHUssgXTYd+6er6PaAhx/QGv4L1EiA== - -"@rollup/rollup-darwin-arm64@4.18.0": - version "4.18.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.18.0.tgz#b6dd74e117510dfe94541646067b0545b42ff096" - integrity sha512-IWfdwU7KDSm07Ty0PuA/W2JYoZ4iTj3TUQjkVsO/6U+4I1jN5lcR71ZEvRh52sDOERdnNhhHU57UITXz5jC1/w== - -"@rollup/rollup-darwin-x64@4.18.0": - version "4.18.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.18.0.tgz#e07d76de1cec987673e7f3d48ccb8e106d42c05c" - integrity sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA== - -"@rollup/rollup-linux-arm-gnueabihf@4.18.0": - version "4.18.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.18.0.tgz#9f1a6d218b560c9d75185af4b8bb42f9f24736b8" - integrity sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA== - -"@rollup/rollup-linux-arm-musleabihf@4.18.0": - version "4.18.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.18.0.tgz#53618b92e6ffb642c7b620e6e528446511330549" - integrity sha512-l3m9ewPgjQSXrUMHg93vt0hYCGnrMOcUpTz6FLtbwljo2HluS4zTXFy2571YQbisTnfTKPZ01u/ukJdQTLGh9A== - -"@rollup/rollup-linux-arm64-gnu@4.18.0": - version "4.18.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.18.0.tgz#99a7ba5e719d4f053761a698f7b52291cefba577" - integrity sha512-rJ5D47d8WD7J+7STKdCUAgmQk49xuFrRi9pZkWoRD1UeSMakbcepWXPF8ycChBoAqs1pb2wzvbY6Q33WmN2ftw== - -"@rollup/rollup-linux-arm64-musl@4.18.0": - version "4.18.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.18.0.tgz#f53db99a45d9bc00ce94db8a35efa7c3c144a58c" - integrity sha512-be6Yx37b24ZwxQ+wOQXXLZqpq4jTckJhtGlWGZs68TgdKXJgw54lUUoFYrg6Zs/kjzAQwEwYbp8JxZVzZLRepQ== - -"@rollup/rollup-linux-powerpc64le-gnu@4.18.0": - version "4.18.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.18.0.tgz#cbb0837408fe081ce3435cf3730e090febafc9bf" - integrity sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA== - -"@rollup/rollup-linux-riscv64-gnu@4.18.0": - version "4.18.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.18.0.tgz#8ed09c1d1262ada4c38d791a28ae0fea28b80cc9" - integrity sha512-ROCM7i+m1NfdrsmvwSzoxp9HFtmKGHEqu5NNDiZWQtXLA8S5HBCkVvKAxJ8U+CVctHwV2Gb5VUaK7UAkzhDjlg== - -"@rollup/rollup-linux-s390x-gnu@4.18.0": - version "4.18.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.18.0.tgz#938138d3c8e0c96f022252a28441dcfb17afd7ec" - integrity sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg== +"@remix-run/router@1.17.1": + version "1.17.1" + resolved "https://registry.npmjs.org/@remix-run/router/-/router-1.17.1.tgz" + integrity sha512-mCOMec4BKd6BRGBZeSnGiIgwsbLGp3yhVqAD8H+PxiRNEHgDpZb8J1TnrSDlg97t0ySKMQJTHCWBCmBpSmkF6Q== "@rollup/rollup-linux-x64-gnu@4.18.0": version "4.18.0" @@ -379,46 +214,6 @@ resolved "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.18.0.tgz" integrity sha512-LKaqQL9osY/ir2geuLVvRRs+utWUNilzdE90TpyoX0eNqPzWjRm14oMEE+YLve4k/NAqCdPkGYDaDF5Sw+xBfg== -"@rollup/rollup-win32-arm64-msvc@4.18.0": - version "4.18.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.18.0.tgz#ed6603e93636a96203c6915be4117245c1bd2daf" - integrity sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA== - -"@rollup/rollup-win32-ia32-msvc@4.18.0": - version "4.18.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.18.0.tgz#14e0b404b1c25ebe6157a15edb9c46959ba74c54" - integrity sha512-Txjh+IxBPbkUB9+SXZMpv+b/vnTEtFyfWZgJ6iyCmt2tdx0OF5WhFowLmnh8ENGNpfUlUZkdI//4IEmhwPieNg== - -"@rollup/rollup-win32-x64-msvc@4.18.0": - version "4.18.0" - resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.0.tgz#5d694d345ce36b6ecf657349e03eb87297e68da4" - integrity sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g== - -"@swc/core-darwin-arm64@1.5.25": - version "1.5.25" - resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.5.25.tgz#fca6bb56ae3b6bcb33a12acf49caa58a37e02769" - integrity sha512-YbD0SBgVJS2DM0vwJTU5m7+wOyCjHPBDMf3nCBJQzFZzOLzK11eRW7SzU2jhJHr9HI9sKcNFfN4lIC2Sj+4inA== - -"@swc/core-darwin-x64@1.5.25": - version "1.5.25" - resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.5.25.tgz#6f8764df464b27623f4d0feb3f4a9dfaceb6e2d9" - integrity sha512-OhP4TROT6gQuozn+ah0Y4UidSdgDmxwtQq3lgCUIAxJYErJAQ82/Y0kve2UaNmkSGjOHU+/b4siHPrYTkXOk0Q== - -"@swc/core-linux-arm-gnueabihf@1.5.25": - version "1.5.25" - resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.5.25.tgz#8056be84c2db35366a8980b03137de506e5ec0c7" - integrity sha512-tNmUfrAHxN2gvYPyYNnHx2CYlPO7DGAUuK/bZrqawu++djcg+atAV3eI3XYJgmHId7/sYAlDQ9wjkrOLofFjVg== - -"@swc/core-linux-arm64-gnu@1.5.25": - version "1.5.25" - resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.5.25.tgz#2430eb0d385b396ae6a8a9ea98993f2026ffa08a" - integrity sha512-stzpke+bRaNFM/HrZPRjX0aQZ86S/2DChVCwb8NAV1n5lu9mz1CS750y7WbbtX/KZjk92FsCeRy2qwkvjI0gWw== - -"@swc/core-linux-arm64-musl@1.5.25": - version "1.5.25" - resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.5.25.tgz#a41706f1c81956bfe2359fed7d2b8358cab37555" - integrity sha512-UckUfDYedish/bj2V1jgQDGgouLhyRpG7jgF3mp8jHir11V2K6JiTyjFoz99eOiclS3+hNdr4QLJ+ifrQMJNZw== - "@swc/core-linux-x64-gnu@1.5.25": version "1.5.25" resolved "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.5.25.tgz" @@ -429,21 +224,6 @@ resolved "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.5.25.tgz" integrity sha512-rsepMTgml0EkswWkBpg3Wrjj5eqjwTzZN5omAn1klzXSZnClTrfeHvBuoIJYVr1yx+jmBkqySgME2p7+magUAw== -"@swc/core-win32-arm64-msvc@1.5.25": - version "1.5.25" - resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.5.25.tgz#a055e059d320fe100fb89556f172a8f05ec55589" - integrity sha512-DJDsLBsRBV3uQBShRK2x6fqzABp9RLNVxDUpTTvUjc7qywJ8vS/yn+POK/zCyVEqLagf1z/8D5CEQ+RAIJq1NA== - -"@swc/core-win32-ia32-msvc@1.5.25": - version "1.5.25" - resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.5.25.tgz#820f04b2ef12393dc5c1f18189bb63a6673878b8" - integrity sha512-BARL1ulHol53MEKC1ZVWM3A3FP757UUgG5Q8v97za+4a1SaIgbwvAQyHDxMYWi9+ij+OapK8YnWjJcFa17g8dw== - -"@swc/core-win32-x64-msvc@1.5.25": - version "1.5.25" - resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.5.25.tgz#27883b25867828d88f9289281679e0aff6f5316a" - integrity sha512-o+MHUWrQI9iR6EusEV8eNU2Ezi3KtlhUR4gfptQN5MbVzlgjTvQbhiKpE1GYOxp+0BLBbKRwITKOcdhxfEJ2Uw== - "@swc/core@^1.5.7": version "1.5.25" resolved "https://registry.npmjs.org/@swc/core/-/core-1.5.25.tgz" @@ -477,53 +257,53 @@ "@types/d3-array@^3.0.3": version "3.2.1" - resolved "https://registry.yarnpkg.com/@types/d3-array/-/d3-array-3.2.1.tgz#1f6658e3d2006c4fceac53fde464166859f8b8c5" + resolved "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz" integrity sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg== "@types/d3-color@*": version "3.1.3" - resolved "https://registry.yarnpkg.com/@types/d3-color/-/d3-color-3.1.3.tgz#368c961a18de721da8200e80bf3943fb53136af2" + resolved "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz" integrity sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A== "@types/d3-ease@^3.0.0": version "3.0.2" - resolved "https://registry.yarnpkg.com/@types/d3-ease/-/d3-ease-3.0.2.tgz#e28db1bfbfa617076f7770dd1d9a48eaa3b6c51b" + resolved "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz" integrity sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA== "@types/d3-interpolate@^3.0.1": version "3.0.4" - resolved "https://registry.yarnpkg.com/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz#412b90e84870285f2ff8a846c6eb60344f12a41c" + resolved "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz" integrity sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA== dependencies: "@types/d3-color" "*" "@types/d3-path@*": version "3.1.0" - resolved "https://registry.yarnpkg.com/@types/d3-path/-/d3-path-3.1.0.tgz#2b907adce762a78e98828f0b438eaca339ae410a" + resolved "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.0.tgz" integrity sha512-P2dlU/q51fkOc/Gfl3Ul9kicV7l+ra934qBFXCFhrZMOL6du1TM0pm1ThYvENukyOn5h9v+yMJ9Fn5JK4QozrQ== "@types/d3-scale@^4.0.2": version "4.0.8" - resolved "https://registry.yarnpkg.com/@types/d3-scale/-/d3-scale-4.0.8.tgz#d409b5f9dcf63074464bf8ddfb8ee5a1f95945bb" + resolved "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.8.tgz" integrity sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ== dependencies: "@types/d3-time" "*" "@types/d3-shape@^3.1.0": version "3.1.6" - resolved "https://registry.yarnpkg.com/@types/d3-shape/-/d3-shape-3.1.6.tgz#65d40d5a548f0a023821773e39012805e6e31a72" + resolved "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.6.tgz" integrity sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA== dependencies: "@types/d3-path" "*" "@types/d3-time@*", "@types/d3-time@^3.0.0": version "3.0.3" - resolved "https://registry.yarnpkg.com/@types/d3-time/-/d3-time-3.0.3.tgz#3c186bbd9d12b9d84253b6be6487ca56b54f88be" + resolved "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.3.tgz" integrity sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw== "@types/d3-timer@^3.0.0": version "3.0.2" - resolved "https://registry.yarnpkg.com/@types/d3-timer/-/d3-timer-3.0.2.tgz#70bbda77dc23aa727413e22e214afa3f0e852f70" + resolved "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz" integrity sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw== "@types/estree@1.0.5": @@ -531,6 +311,18 @@ resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz" integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== +"@types/js-cookie@^3.0.6": + version "3.0.6" + resolved "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.6.tgz" + integrity sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ== + +"@types/node@^18.0.0 || >=20.0.0", "@types/node@^20.14.9": + version "20.14.9" + resolved "https://registry.npmjs.org/@types/node/-/node-20.14.9.tgz" + integrity sha512-06OCtnTXtWOZBJlRApleWndH4JsRVs1pDCc8dLSQp+7PpUpX3ePdHyeNSFTeSe7FtKyQkrlPvHwJOW3SLd8Oyg== + dependencies: + undici-types "~5.26.4" + "@types/prop-types@*": version "15.7.12" resolved "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz" @@ -543,7 +335,7 @@ dependencies: "@types/react" "*" -"@types/react@*", "@types/react@^18.2.66": +"@types/react@*", "@types/react@^16.8.0 || ^17.0.0 || ^18.0.0", "@types/react@^16.9.0 || ^17.0.0 || ^18.0.0", "@types/react@^18.2.66": 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== @@ -566,7 +358,7 @@ natural-compare "^1.4.0" ts-api-utils "^1.3.0" -"@typescript-eslint/parser@^7.2.0": +"@typescript-eslint/parser@^7.0.0", "@typescript-eslint/parser@^7.2.0": version "7.12.0" resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.12.0.tgz" integrity sha512-dm/J2UDY3oV3TKius2OUZIFHsomQmpHtsV0FTh1WO8EKgHLQ1QCADUqscPgTpU+ih1e21FQSRjXckHn3txn6kQ== @@ -649,7 +441,7 @@ acorn-jsx@^5.3.2: resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== -acorn@^8.9.0: +"acorn@^6.0.0 || ^7.0.0 || ^8.0.0", acorn@^8.9.0: version "8.11.3" resolved "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz" integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== @@ -720,7 +512,7 @@ callsites@^3.0.0: camelcase-css@^2.0.1: version "2.0.1" - resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5" + resolved "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz" integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== chalk@^4.0.0: @@ -733,7 +525,7 @@ chalk@^4.0.0: clsx@^2.0.0, clsx@^2.1.1: version "2.1.1" - resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999" + resolved "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz" integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA== color-convert@^2.0.1: @@ -764,7 +556,7 @@ cross-spawn@^7.0.2: cssesc@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" + resolved "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz" integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== csstype@^3.0.2: @@ -772,43 +564,43 @@ csstype@^3.0.2: resolved "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz" integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== -"d3-array@2 - 3", "d3-array@2.10.0 - 3", d3-array@^3.1.6: +d3-array@^3.1.6, "d3-array@2 - 3", "d3-array@2.10.0 - 3": version "3.2.4" - resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-3.2.4.tgz#15fec33b237f97ac5d7c986dc77da273a8ed0bb5" + resolved "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz" integrity sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg== dependencies: internmap "1 - 2" "d3-color@1 - 3": version "3.1.0" - resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-3.1.0.tgz#395b2833dfac71507f12ac2f7af23bf819de24e2" + resolved "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz" integrity sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA== d3-ease@^3.0.1: version "3.0.1" - resolved "https://registry.yarnpkg.com/d3-ease/-/d3-ease-3.0.1.tgz#9658ac38a2140d59d346160f1f6c30fda0bd12f4" + resolved "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz" integrity sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w== "d3-format@1 - 3": version "3.1.0" - resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-3.1.0.tgz#9260e23a28ea5cb109e93b21a06e24e2ebd55641" + resolved "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz" integrity sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA== -"d3-interpolate@1.2.0 - 3", d3-interpolate@^3.0.1: +d3-interpolate@^3.0.1, "d3-interpolate@1.2.0 - 3": version "3.0.1" - resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz#3c47aa5b32c5b3dfb56ef3fd4342078a632b400d" + resolved "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz" integrity sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g== dependencies: d3-color "1 - 3" d3-path@^3.1.0: version "3.1.0" - resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-3.1.0.tgz#22df939032fb5a71ae8b1800d61ddb7851c42526" + resolved "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz" integrity sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ== d3-scale@^4.0.2: version "4.0.2" - resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-4.0.2.tgz#82b38e8e8ff7080764f8dcec77bd4be393689396" + resolved "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz" integrity sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ== dependencies: d3-array "2.10.0 - 3" @@ -819,33 +611,33 @@ d3-scale@^4.0.2: d3-shape@^3.1.0: version "3.2.0" - resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-3.2.0.tgz#a1a839cbd9ba45f28674c69d7f855bcf91dfc6a5" + resolved "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz" integrity sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA== dependencies: d3-path "^3.1.0" "d3-time-format@2 - 4": version "4.1.0" - resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-4.1.0.tgz#7ab5257a5041d11ecb4fe70a5c7d16a195bb408a" + resolved "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz" integrity sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg== dependencies: d3-time "1 - 3" -"d3-time@1 - 3", "d3-time@2.1.1 - 3", d3-time@^3.0.0: +d3-time@^3.0.0, "d3-time@1 - 3", "d3-time@2.1.1 - 3": version "3.1.0" - resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-3.1.0.tgz#9310db56e992e3c0175e1ef385e545e48a9bb5c7" + resolved "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz" integrity sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q== dependencies: d3-array "2 - 3" d3-timer@^3.0.1: version "3.0.1" - resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-3.0.1.tgz#6284d2a2708285b1abb7e201eda4380af35e63b0" + resolved "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz" integrity sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA== -dayjs@^1.11.11: +dayjs@^1.11.11, dayjs@>=1.0.0: version "1.11.11" - resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.11.tgz#dfe0e9d54c5f8b68ccf8ca5f72ac603e7e5ed59e" + resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.11.11.tgz" integrity sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg== debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: @@ -857,7 +649,7 @@ debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: decimal.js-light@^2.4.1: version "2.5.1" - resolved "https://registry.yarnpkg.com/decimal.js-light/-/decimal.js-light-2.5.1.tgz#134fd32508f19e208f4fb2f8dac0d2626a867934" + resolved "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz" integrity sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg== deep-is@^0.1.3: @@ -867,7 +659,7 @@ deep-is@^0.1.3: detect-node-es@^1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/detect-node-es/-/detect-node-es-1.1.0.tgz#163acdf643330caa0b4cd7c21e7ee7755d6fa493" + resolved "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz" integrity sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ== dir-glob@^3.0.1: @@ -886,12 +678,17 @@ doctrine@^3.0.0: dom-helpers@^5.0.1: version "5.2.1" - resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902" + resolved "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz" integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA== dependencies: "@babel/runtime" "^7.8.7" csstype "^3.0.2" +dotenv@^16.4.5: + version "16.4.5" + resolved "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz" + integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg== + esbuild@^0.20.1: version "0.20.2" resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz" @@ -949,7 +746,7 @@ eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4 resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz" integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== -eslint@^8.57.0: +"eslint@^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0", "eslint@^6.0.0 || ^7.0.0 || >=8.0.0", eslint@^8.56.0, eslint@^8.57.0, eslint@>=7: version "8.57.0" resolved "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz" integrity sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ== @@ -1028,7 +825,7 @@ esutils@^2.0.2: eventemitter3@^4.0.1: version "4.0.7" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" + resolved "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz" integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: @@ -1038,7 +835,7 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: fast-equals@^5.0.1: version "5.0.1" - resolved "https://registry.yarnpkg.com/fast-equals/-/fast-equals-5.0.1.tgz#a4eefe3c5d1c0d021aeed0bc10ba5e0c12ee405d" + resolved "https://registry.npmjs.org/fast-equals/-/fast-equals-5.0.1.tgz" integrity sha512-WF1Wi8PwwSY7/6Kx0vKXtw8RwuSGoM1bvDaJbu7MxDlR1vovZjIAKrnzyrThgAjm6JDTu0fVgWXDlMGspodfoQ== fast-glob@^3.2.11, fast-glob@^3.2.9: @@ -1110,14 +907,9 @@ fs.realpath@^1.0.0: resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== -fsevents@~2.3.2, fsevents@~2.3.3: - version "2.3.3" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" - integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== - get-nonce@^1.0.0: version "1.0.1" - resolved "https://registry.yarnpkg.com/get-nonce/-/get-nonce-1.0.1.tgz#fdf3f0278073820d2ce9426c18f07481b1e0cdf3" + resolved "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz" integrity sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q== glob-parent@^5.1.2: @@ -1177,7 +969,7 @@ has-flag@^4.0.0: highlight.js@^11.9.0: version "11.9.0" - resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.9.0.tgz#04ab9ee43b52a41a047432c8103e2158a1b8b5b0" + resolved "https://registry.npmjs.org/highlight.js/-/highlight.js-11.9.0.tgz" integrity sha512-fJ7cW7fQGCYAkgv4CPfwFHrfd/cLS4Hau96JuJ+ZTOWhjnhoeN1ub1tFmALm/+lW5z4WCAuAV9bm05AP0mS6Gw== ignore@^5.2.0, ignore@^5.3.1: @@ -1213,12 +1005,12 @@ inherits@2: "internmap@1 - 2": version "2.0.3" - resolved "https://registry.yarnpkg.com/internmap/-/internmap-2.0.3.tgz#6685f23755e43c524e251d29cbc97248e3061009" + resolved "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz" integrity sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg== invariant@^2.2.4: version "2.2.4" - resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" + resolved "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz" integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== dependencies: loose-envify "^1.0.0" @@ -1250,6 +1042,11 @@ isexe@^2.0.0: resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== +js-cookie@^3.0.5: + version "3.0.5" + resolved "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz" + integrity sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw== + "js-tokens@^3.0.0 || ^4.0.0": version "4.0.0" resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" @@ -1286,7 +1083,7 @@ keyv@^4.5.3: klona@^2.0.6: version "2.0.6" - resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.6.tgz#85bffbf819c03b2f53270412420a4555ef882e22" + resolved "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz" integrity sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA== levn@^0.4.1: @@ -1311,7 +1108,7 @@ lodash.merge@^4.6.2: lodash@^4.17.21: version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: @@ -1334,7 +1131,21 @@ micromatch@^4.0.4: braces "^3.0.3" picomatch "^2.3.1" -minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: +minimatch@^3.0.5: + version "3.1.2" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimatch@^3.1.1: + version "3.1.2" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimatch@^3.1.2: version "3.1.2" resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== @@ -1365,7 +1176,7 @@ natural-compare@^1.4.0: object-assign@^4.1.1: version "4.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz" integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== once@^1.3.0: @@ -1440,14 +1251,14 @@ picomatch@^2.3.1: postcss-js@^4.0.0: version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-js/-/postcss-js-4.0.1.tgz#61598186f3703bab052f1c4f7d805f3991bee9d2" + resolved "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz" integrity sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw== dependencies: camelcase-css "^2.0.1" postcss-mixins@^9.0.4: version "9.0.4" - resolved "https://registry.yarnpkg.com/postcss-mixins/-/postcss-mixins-9.0.4.tgz#75cd3cdb619a7e08c4c51ebb094db5f6d65b3831" + resolved "https://registry.npmjs.org/postcss-mixins/-/postcss-mixins-9.0.4.tgz" integrity sha512-XVq5jwQJDRu5M1XGkdpgASqLk37OqkH4JCFDXl/Dn7janOJjCTEKL+36cnRVy7bMtoBzALfO7bV7nTIsFnUWLA== dependencies: fast-glob "^3.2.11" @@ -1457,14 +1268,14 @@ postcss-mixins@^9.0.4: postcss-nested@^6.0.1: version "6.0.1" - resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-6.0.1.tgz#f83dc9846ca16d2f4fa864f16e9d9f7d0961662c" + resolved "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz" integrity sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ== dependencies: postcss-selector-parser "^6.0.11" postcss-preset-mantine@^1.15.0: version "1.15.0" - resolved "https://registry.yarnpkg.com/postcss-preset-mantine/-/postcss-preset-mantine-1.15.0.tgz#011b4770f185c10256bea65b8b0999a2e2d64cc0" + resolved "https://registry.npmjs.org/postcss-preset-mantine/-/postcss-preset-mantine-1.15.0.tgz" integrity sha512-OKPs6uoORSXlU/GFH1ZtFaslecHBPwuoSikdL5W3WKJm4ZPAQM0mw9x9m3toa/Mo1JhoBmYMM28i+zEdav5Edg== dependencies: postcss-mixins "^9.0.4" @@ -1472,7 +1283,7 @@ postcss-preset-mantine@^1.15.0: postcss-selector-parser@^6.0.11: version "6.1.0" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.1.0.tgz#49694cb4e7c649299fea510a29fa6577104bcf53" + resolved "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.0.tgz" integrity sha512-UMz42UD0UY0EApS0ZL9o1XnLhSTtvvvLe5Dc2H2O56fvRZi+KulDyf5ctDhhtYJBGKStV2FL1fy6253cmLgqVQ== dependencies: cssesc "^3.0.0" @@ -1480,12 +1291,12 @@ postcss-selector-parser@^6.0.11: postcss-simple-vars@^7.0.0, postcss-simple-vars@^7.0.1: version "7.0.1" - resolved "https://registry.yarnpkg.com/postcss-simple-vars/-/postcss-simple-vars-7.0.1.tgz#836b3097a54dcd13dbd3c36a5dbdd512fad2954c" + resolved "https://registry.npmjs.org/postcss-simple-vars/-/postcss-simple-vars-7.0.1.tgz" integrity sha512-5GLLXaS8qmzHMOjVxqkk1TZPf1jMqesiI7qLhnlyERalG0sMbHIbJqrcnrpmZdKCLglHnRHoEBB61RtGTsj++A== -postcss@^8.4.38: +postcss@^8.2.1, postcss@^8.2.14, postcss@^8.3.3, postcss@^8.4.21, postcss@^8.4.38, postcss@>=8.0.0: version "8.4.38" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.38.tgz#b387d533baf2054288e337066d81c6bee9db9e0e" + resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz" integrity sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A== dependencies: nanoid "^3.3.7" @@ -1499,7 +1310,7 @@ prelude-ls@^1.2.1: prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: version "15.8.1" - resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" + resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== dependencies: loose-envify "^1.4.0" @@ -1516,7 +1327,7 @@ queue-microtask@^1.2.2: resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== -react-dom@^18.2.0: +"react-dom@^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0", "react-dom@^16.0.0 || ^17.0.0 || ^18.0.0", "react-dom@^16.8.0 || ^17.0.0 || ^18.0.0", react-dom@^18.2.0, react-dom@>=16.6.0, react-dom@>=16.8, react-dom@>=16.8.0: version "18.3.1" resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz" integrity sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw== @@ -1526,19 +1337,19 @@ react-dom@^18.2.0: react-is@^16.10.2, react-is@^16.13.1: version "16.13.1" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" + resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== react-number-format@^5.3.1: version "5.4.0" - resolved "https://registry.yarnpkg.com/react-number-format/-/react-number-format-5.4.0.tgz#8c1e97add1970d1a2f372ca286bcdaa49632ba5c" + resolved "https://registry.npmjs.org/react-number-format/-/react-number-format-5.4.0.tgz" integrity sha512-NWdICrqLhI7rAS8yUeLVd6Wr4cN7UjJ9IBTS0f/a9i7UB4x4Ti70kGnksBtZ7o4Z7YRbvCMMR/jQmkoOBa/4fg== dependencies: prop-types "^15.7.2" react-remove-scroll-bar@^2.3.6: version "2.3.6" - resolved "https://registry.yarnpkg.com/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.6.tgz#3e585e9d163be84a010180b18721e851ac81a29c" + resolved "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.6.tgz" integrity sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g== dependencies: react-style-singleton "^2.2.1" @@ -1546,7 +1357,7 @@ react-remove-scroll-bar@^2.3.6: react-remove-scroll@^2.5.7: version "2.5.10" - resolved "https://registry.yarnpkg.com/react-remove-scroll/-/react-remove-scroll-2.5.10.tgz#5fae456a23962af6d3c38ca1978bcfe0806c4061" + resolved "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.10.tgz" integrity sha512-m3zvBRANPBw3qxVVjEIPEQinkcwlFZ4qyomuWVpNJdv4c6MvHfXV0C3L9Jx5rr3HeBHKNRX+1jreB5QloDIJjA== dependencies: react-remove-scroll-bar "^2.3.6" @@ -1555,24 +1366,24 @@ react-remove-scroll@^2.5.7: use-callback-ref "^1.3.0" use-sidecar "^1.1.2" -react-router-dom@^6.23.1: - version "6.23.1" - resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.23.1.tgz#30cbf266669693e9492aa4fc0dde2541ab02322f" - integrity sha512-utP+K+aSTtEdbWpC+4gxhdlPFwuEfDKq8ZrPFU65bbRJY+l706qjR7yaidBpo3MSeA/fzwbXWbKBI6ftOnP3OQ== +react-router-dom@^6.24.1: + version "6.24.1" + resolved "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.24.1.tgz" + integrity sha512-U19KtXqooqw967Vw0Qcn5cOvrX5Ejo9ORmOtJMzYWtCT4/WOfFLIZGGsVLxcd9UkBO0mSTZtXqhZBsWlHr7+Sg== dependencies: - "@remix-run/router" "1.16.1" - react-router "6.23.1" + "@remix-run/router" "1.17.1" + react-router "6.24.1" -react-router@6.23.1: - version "6.23.1" - resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.23.1.tgz#d08cbdbd9d6aedc13eea6e94bc6d9b29cb1c4be9" - integrity sha512-fzcOaRF69uvqbbM7OhvQyBTFDVrrGlsFdS3AL+1KfIBtGETibHzi3FkoTRyiDJnWNc2VxrfvR+657ROHjaNjqQ== +react-router@6.24.1: + version "6.24.1" + resolved "https://registry.npmjs.org/react-router/-/react-router-6.24.1.tgz" + integrity sha512-PTXFXGK2pyXpHzVo3rR9H7ip4lSPZZc0bHG5CARmj65fTT6qG7sTngmb6lcYu1gf3y/8KxORoy9yn59pGpCnpg== dependencies: - "@remix-run/router" "1.16.1" + "@remix-run/router" "1.17.1" react-smooth@^4.0.0: version "4.0.1" - resolved "https://registry.yarnpkg.com/react-smooth/-/react-smooth-4.0.1.tgz#6200d8699bfe051ae40ba187988323b1449eab1a" + resolved "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.1.tgz" integrity sha512-OE4hm7XqR0jNOq3Qmk9mFLyd6p2+j6bvbPJ7qlB7+oo0eNcL2l7WQzG6MBnT3EXY6xzkLMUBec3AfewJdA0J8w== dependencies: fast-equals "^5.0.1" @@ -1581,7 +1392,7 @@ react-smooth@^4.0.0: react-style-singleton@^2.2.1: version "2.2.1" - resolved "https://registry.yarnpkg.com/react-style-singleton/-/react-style-singleton-2.2.1.tgz#f99e420492b2d8f34d38308ff660b60d0b1205b4" + resolved "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz" integrity sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g== dependencies: get-nonce "^1.0.0" @@ -1590,16 +1401,16 @@ react-style-singleton@^2.2.1: react-textarea-autosize@8.5.3: version "8.5.3" - resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-8.5.3.tgz#d1e9fe760178413891484847d3378706052dd409" + resolved "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.5.3.tgz" integrity sha512-XT1024o2pqCuZSuBt9FwHlaDeNtVrtCXu0Rnz88t1jUGheCLa3PhjE1GH8Ctm2axEtvdCl5SUHYschyQ0L5QHQ== dependencies: "@babel/runtime" "^7.20.13" use-composed-ref "^1.3.0" use-latest "^1.2.1" -react-transition-group@4.4.5, react-transition-group@^4.4.5: +react-transition-group@^4.4.5, react-transition-group@4.4.5: version "4.4.5" - resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1" + resolved "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz" integrity sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g== dependencies: "@babel/runtime" "^7.5.5" @@ -1607,7 +1418,7 @@ react-transition-group@4.4.5, react-transition-group@^4.4.5: loose-envify "^1.4.0" prop-types "^15.6.2" -react@^18.2.0: +"react@^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0", "react@^16.0.0 || ^17.0.0 || ^18.0.0", "react@^16.8.0 || ^17.0.0 || ^18.0.0", react@^18.2.0, react@^18.3.1, react@>=16.6.0, react@>=16.8, react@>=16.8.0: 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== @@ -1616,14 +1427,14 @@ react@^18.2.0: recharts-scale@^0.4.4: version "0.4.5" - resolved "https://registry.yarnpkg.com/recharts-scale/-/recharts-scale-0.4.5.tgz#0969271f14e732e642fcc5bd4ab270d6e87dd1d9" + resolved "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz" integrity sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w== dependencies: decimal.js-light "^2.4.1" -recharts@2: +recharts@^2.10.3, recharts@2: version "2.12.7" - resolved "https://registry.yarnpkg.com/recharts/-/recharts-2.12.7.tgz#c7f42f473a257ff88b43d88a92530930b5f9e773" + resolved "https://registry.npmjs.org/recharts/-/recharts-2.12.7.tgz" integrity sha512-hlLJMhPQfv4/3NBSAyq3gzGg4h2v69RJh6KU7b3pXYNNAELs9kEoXOjbkxdXpALqKBoVmVptGfLpxdaVYqjmXQ== dependencies: clsx "^2.0.0" @@ -1637,7 +1448,7 @@ recharts@2: regenerator-runtime@^0.14.0: version "0.14.1" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f" + resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz" integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw== resolve-from@^4.0.0: @@ -1735,9 +1546,9 @@ strip-json-comments@^3.1.1: resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== -sugarss@^4.0.1: +sugarss@*, sugarss@^4.0.1: version "4.0.1" - resolved "https://registry.yarnpkg.com/sugarss/-/sugarss-4.0.1.tgz#128a783ed71ee0fc3b489ce1f7d5a89bc1e24383" + resolved "https://registry.npmjs.org/sugarss/-/sugarss-4.0.1.tgz" integrity sha512-WCjS5NfuVJjkQzK10s8WOBY+hhDxxNt/N6ZaGwxFZ+wN3/lKKFSaaKUNecULcTTvE4urLcKaZFQD8vO0mOZujw== supports-color@^7.1.0: @@ -1749,7 +1560,7 @@ supports-color@^7.1.0: tabbable@^6.0.0: version "6.2.0" - resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-6.2.0.tgz#732fb62bc0175cfcec257330be187dcfba1f3b97" + resolved "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz" integrity sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew== text-table@^0.2.0: @@ -1759,7 +1570,7 @@ text-table@^0.2.0: tiny-invariant@^1.3.1: version "1.3.3" - resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz#46680b7a873a0d5d10005995eb90a70d74d60127" + resolved "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz" integrity sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg== to-regex-range@^5.0.1: @@ -1776,7 +1587,7 @@ ts-api-utils@^1.3.0: tslib@^2.0.0, tslib@^2.1.0: version "2.6.3" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.3.tgz#0438f810ad7a9edcde7a241c3d80db693c8cbfe0" + resolved "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz" integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ== type-check@^0.4.0, type-check@~0.4.0: @@ -1793,14 +1604,19 @@ type-fest@^0.20.2: type-fest@^4.12.0: version "4.19.0" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.19.0.tgz#f7d3d5f55a7a118b5fe3d2eef53059cf8e516dcd" + resolved "https://registry.npmjs.org/type-fest/-/type-fest-4.19.0.tgz" integrity sha512-CN2l+hWACRiejlnr68vY0/7734Kzu+9+TOslUXbSCQ1ruY9XIHDBSceVXCcHm/oXrdzhtLMMdJEKfemf1yXiZQ== -typescript@^5.2.2: +typescript@^5.2.2, typescript@>=4.2.0: version "5.4.5" resolved "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz" integrity sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ== +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + uri-js@^4.2.2: version "4.4.1" resolved "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz" @@ -1810,31 +1626,31 @@ uri-js@^4.2.2: use-callback-ref@^1.3.0: version "1.3.2" - resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.3.2.tgz#6134c7f6ff76e2be0b56c809b17a650c942b1693" + resolved "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.2.tgz" integrity sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA== dependencies: tslib "^2.0.0" use-composed-ref@^1.3.0: version "1.3.0" - resolved "https://registry.yarnpkg.com/use-composed-ref/-/use-composed-ref-1.3.0.tgz#3d8104db34b7b264030a9d916c5e94fbe280dbda" + resolved "https://registry.npmjs.org/use-composed-ref/-/use-composed-ref-1.3.0.tgz" integrity sha512-GLMG0Jc/jiKov/3Ulid1wbv3r54K9HlMW29IWcDFPEqFkSO2nS0MuefWgMJpeHQ9YJeXDL3ZUF+P3jdXlZX/cQ== use-isomorphic-layout-effect@^1.1.1: version "1.1.2" - resolved "https://registry.yarnpkg.com/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz#497cefb13d863d687b08477d9e5a164ad8c1a6fb" + resolved "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz" integrity sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA== use-latest@^1.2.1: version "1.2.1" - resolved "https://registry.yarnpkg.com/use-latest/-/use-latest-1.2.1.tgz#d13dfb4b08c28e3e33991546a2cee53e14038cf2" + resolved "https://registry.npmjs.org/use-latest/-/use-latest-1.2.1.tgz" integrity sha512-xA+AVm/Wlg3e2P/JiItTziwS7FK92LWrDB0p+hgXloIMuVCeJJ8v6f0eeHyPZaJrM+usM1FkFfbNCrJGs8A/zw== dependencies: use-isomorphic-layout-effect "^1.1.1" use-sidecar@^1.1.2: version "1.1.2" - resolved "https://registry.yarnpkg.com/use-sidecar/-/use-sidecar-1.1.2.tgz#2f43126ba2d7d7e117aa5855e5d8f0276dfe73c2" + resolved "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz" integrity sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw== dependencies: detect-node-es "^1.1.0" @@ -1842,12 +1658,12 @@ use-sidecar@^1.1.2: util-deprecate@^1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== victory-vendor@^36.6.8: version "36.9.2" - resolved "https://registry.yarnpkg.com/victory-vendor/-/victory-vendor-36.9.2.tgz#668b02a448fa4ea0f788dbf4228b7e64669ff801" + resolved "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz" integrity sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ== dependencies: "@types/d3-array" "^3.0.3" @@ -1865,7 +1681,7 @@ victory-vendor@^36.6.8: d3-time "^3.0.0" d3-timer "^3.0.1" -vite@^5.2.0: +"vite@^4 || ^5", vite@^5.2.0: version "5.2.12" resolved "https://registry.npmjs.org/vite/-/vite-5.2.12.tgz" integrity sha512-/gC8GxzxMK5ntBwb48pR32GGhENnjtY30G4A0jemunsBkiEZFw60s8InGpN8gkhHEkjnRK1aSAxeQgwvFhUHAA== From 5af994ba94fce92ef4be3ec4ce1d39a6d1a59f90 Mon Sep 17 00:00:00 2001 From: Treyson Date: Wed, 10 Jul 2024 15:30:15 -0600 Subject: [PATCH 09/96] to a point where you can login, and query for the logs api, displaying them on the home. This is all still demo territory, figuring how to interact with everything --- package-lock.json | 16 + package.json | 2 + src/api.ts | 5 +- src/components/Home.tsx | 77 +++-- src/components/Logslist.tsx | 5 + src/hooks/useApi.ts | 440 ++++++++++++++++++++++++++++ src/hooks/useCsrfToken.ts | 5 +- src/types/api.ts | 536 ++++++++++++++++++++++++++++++++++ src/types/apiFilters.ts | 130 +++++++++ src/types/index.ts | 65 +++++ src/utilities/apiFunctions.ts | 285 ++++++++++++++++-- src/utilities/dateUtils.ts | 49 ++++ src/utilities/getCookie.ts | 14 + yarn.lock | 10 + 14 files changed, 1594 insertions(+), 45 deletions(-) create mode 100644 src/components/Logslist.tsx create mode 100644 src/hooks/useApi.ts create mode 100644 src/types/api.ts create mode 100644 src/types/apiFilters.ts create mode 100644 src/types/index.ts create mode 100644 src/utilities/dateUtils.ts create mode 100644 src/utilities/getCookie.ts diff --git a/package-lock.json b/package-lock.json index 4faa811..e2c427f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "@mantine/spotlight": "^7.10.1", "dayjs": "^1.11.11", "js-cookie": "^3.0.5", + "object-hash": "^3.0.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.24.1", @@ -28,6 +29,7 @@ "devDependencies": { "@types/js-cookie": "^3.0.6", "@types/node": "^20.14.9", + "@types/object-hash": "^3.0.6", "@types/react": "^18.2.66", "@types/react-dom": "^18.2.22", "@typescript-eslint/eslint-plugin": "^7.2.0", @@ -1285,6 +1287,12 @@ "undici-types": "~5.26.4" } }, + "node_modules/@types/object-hash": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/object-hash/-/object-hash-3.0.6.tgz", + "integrity": "sha512-fOBV8C1FIu2ELinoILQ+ApxcUKz4ngq+IWUYrxSGjXzzjUALijilampwkMgEtJ+h2njAW3pi853QpzNVCHB73w==", + "dev": true + }, "node_modules/@types/prop-types": { "version": "15.7.12", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", @@ -2718,6 +2726,14 @@ "node": ">=0.10.0" } }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "engines": { + "node": ">= 6" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", diff --git a/package.json b/package.json index 41af234..88c98fd 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "@mantine/spotlight": "^7.10.1", "dayjs": "^1.11.11", "js-cookie": "^3.0.5", + "object-hash": "^3.0.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.24.1", @@ -30,6 +31,7 @@ "devDependencies": { "@types/js-cookie": "^3.0.6", "@types/node": "^20.14.9", + "@types/object-hash": "^3.0.6", "@types/react": "^18.2.66", "@types/react-dom": "^18.2.22", "@typescript-eslint/eslint-plugin": "^7.2.0", diff --git a/src/api.ts b/src/api.ts index 7e4d02c..9795634 100644 --- a/src/api.ts +++ b/src/api.ts @@ -1,4 +1,4 @@ -export async function getCSRFToken(apiUrl: string): Promise { +export async function getCSRFToken(apiUrl: string): Promise<{ csrfToken: string, sessionID: string }> { try { const response = await fetch(`${apiUrl}/get_csrf/`, { method: 'GET', @@ -11,7 +11,8 @@ export async function getCSRFToken(apiUrl: string): Promise { throw new Error('Failed to fetch CSRF token'); } const data = await response.json(); - return data.csrfToken; + console.log(data) + return { csrfToken: data.csrfToken, sessionID: data.session_id }; } catch (error) { console.error('Error fetching CSRF token:', error); throw error; diff --git a/src/components/Home.tsx b/src/components/Home.tsx index cf1822c..1bd0ef9 100644 --- a/src/components/Home.tsx +++ b/src/components/Home.tsx @@ -4,19 +4,38 @@ import { useNavigate } from 'react-router-dom'; import { useConfig } from '../contexts/ConfigContext'; import { apiCall } from '../api'; import { useCsrfToken } from '../hooks/useCsrfToken'; +import { getApiEndpointFunctions } from '../utilities/apiFunctions'; +import { usePaginatedApi } from '../hooks/useApi'; const Home = (): JSX.Element => { + const [page, setPage] = useState(1); + const [data, setData] = useState([]); const navigate = useNavigate(); const { config } = useConfig(); const csrftoken = useCsrfToken(); const [userName, setUserName] = useState(null); + const api = getApiEndpointFunctions(); + + const { data: paginatedData, loading, reload } = usePaginatedApi(api.logs.get, page, 10); + + useEffect(() => { + if (paginatedData && paginatedData.results) { + setData(paginatedData.results); + } + }, [paginatedData]); + + const nextPage = async () => { + setPage(page + 1); + }; + + const handleLogout = async () => { try { const url = `${config.apiUrl}/logout/`; const data = await apiCall(url, 'POST', null, csrftoken); console.log(data); - navigate('/login') + navigate('/login'); } catch (error) { console.error('There was a problem with the fetch operation:', error); } @@ -27,7 +46,7 @@ const Home = (): JSX.Element => { const url = `${config.apiUrl}/whoami/`; const data = await apiCall(url, 'GET', null, csrftoken); if (data.user === null) { - navigate('/login') + navigate('/login'); return; } setUserName(data.user.username); @@ -41,25 +60,43 @@ const Home = (): JSX.Element => { }, []); return ( - <> - - - Home - Welcome, {userName} - - - - - - + + + Home + Welcome, {userName} + + + + {data && data.map((item: any) => ( +
+ ID: {item.id} + Content Type: {item.content_type} + Action Flag: {item.action_flag} + Action Time: {item.action_time} + Object ID: {item.object_id} + Object Repr: {item.object_repr} + Change Message: {item.change_message} + User: {item.user} +
+ ))} +
+
+
); }; diff --git a/src/components/Logslist.tsx b/src/components/Logslist.tsx new file mode 100644 index 0000000..af5cfae --- /dev/null +++ b/src/components/Logslist.tsx @@ -0,0 +1,5 @@ +const LogsList = () => { + +} + +export default LogsList; \ No newline at end of file diff --git a/src/hooks/useApi.ts b/src/hooks/useApi.ts new file mode 100644 index 0000000..9d2a0ee --- /dev/null +++ b/src/hooks/useApi.ts @@ -0,0 +1,440 @@ +import { + Dispatch, + SetStateAction, + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from "react"; +import { + DataRequest, + MemoizableRequest, + QueryRequest, +} from "../utilities/apiFunctions"; +import { PaginatedData } from "../types/api"; +import objectHash from "object-hash"; + +const queryParamsEqual = >( + a: T, + b: T +) => { + return ( + Object.keys(a).every((key) => a[key] === b[key]) && + Object.keys(b).every((key) => a[key] === b[key]) + ); +}; + +const useQueryParamsMemo = < + T extends Record +>( + queryParams: T +) => { + const [memo, setMemo] = useState(queryParams); + // update memo if queryParams change + if (!queryParamsEqual(queryParams, memo)) { + setMemo(queryParams); + } + return memo; +}; + +const useApiEndpointMemo = (endpoint: MemoizableRequest) => { + const [memo, setMemo] = useState(() => endpoint); + if (endpoint.endpoint !== memo.endpoint) { + setMemo(endpoint); + } + return memo; +}; + +/** + * Fetch JSON data from a KCM API endpoint with query parameters + * @param endpoint The API endpoint function to fetch data from + * @param queryParams The optional query parameters to include in the request + * @param transform A function to transform the fetched data before returning it, called once after the request completes + * @param makeRequest Whether to make requests or not + * @returns The fetched data, null if the request has not completed + */ +export const useApiData = < + Endpoint extends QueryRequest | DataRequest | null, + Return = Awaited>> +>( + endpoint: Endpoint, + queryParams?: Parameters[0], + transform: ( + data: Awaited> + ) => Return = (data) => data, + makeRequest: boolean = true +): + | { + loading: false; + data: Return; + /** + * The error that occurred while fetching the data, if any. + */ + error: Error | undefined; + /** + * Perform an immediate reload of the data without modifying the query parameters. + */ + reload: () => Promise; + } + | { + loading: true; + data: undefined; + /** + * The error that occurred while fetching the data, if any. + */ + error: Error | undefined; + /** + * Perform an immediate reload of the data without modifying the query parameters. + */ + reload: () => Promise; + } => { + const [data, setData] = useState(undefined); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(undefined); + const memoizedQueryParams = useQueryParamsMemo(queryParams ?? {}); + const memoizedEndpoint = useApiEndpointMemo( + (endpoint as MemoizableRequest) ?? {} + ); + const controller = useRef(null); + /** + * Perform an immediate reload of the data without modifying the query parameters. + * This is useful for refreshing the data after a mutation. + */ + const reload = useCallback(async () => { + if (!makeRequest) { + return; + } + setLoading(true); + if (controller.current) { + controller.current.abort(); + } + controller.current = new AbortController(); + if (endpoint === null) { + setData(undefined); + setLoading(false); + return; + } + return memoizedEndpoint(memoizedQueryParams, {}, controller.current) + .then(transform) + .then(setData) + .then(setLoading.bind(this, false)) + .catch((e) => { + if (e instanceof DOMException && e.name === "AbortError") { + // Request was aborted, ignore. We did this to ourselves. + return; + } + console.error( + `Failed to fetch data from ${memoizedEndpoint.endpoint}`, + e + ); + if (e instanceof Error) { + setError(e); + } + setData(undefined); + }) + .finally(() => { + controller.current = null; + }); + }, [makeRequest, memoizedEndpoint, memoizedQueryParams, transform]); + + useEffect(() => { + reload(); + return () => { + if (controller.current) { + controller.current.abort(); + } + }; + }, [memoizedEndpoint, memoizedQueryParams, makeRequest]); + + // @ts-ignore data is always undefined when loading is true + return { + data, + loading, + error, + reload: useCallback(reload, [memoizedEndpoint, memoizedQueryParams]), + } as const; +}; + +/** + * Fetch paginated JSON data from a KCM API endpoint with query parameters + * @param endpoint The API endpoint function to fetch data from + * @param page The page number to fetch + * @param pageSize The number of items per page + * @param queryParams The optional query parameters to include in the request + * @param transform A function to transform the fetched data before returning it, called once after the request completes + * @returns The fetched data, undefined if the request has not completed + */ +export const usePaginatedApi = < + Endpoint extends QueryRequest>, + Return = Awaited> +>( + endpoint: Endpoint, + page: number, + pageSize: number, + queryParams?: Omit[0], "page" | "page_size">, + transform: (data: Awaited>) => Return = (data) => + data as Return +) => { + const dataParams = useMemo( + () => ({ + ...queryParams, + page: (page), + page_size: pageSize, + }), + [page, pageSize, queryParams] + ); + return useApiData(endpoint, dataParams, transform); +}; + +/** + * Fetch JSON data from a KCM API list endpoint and cache the results. Uses a LRU cache to store the last `cacheSize` pages. + * @param endpoint The API endpoint function to fetch data from + * @param prefetch How many pages to "prefetch" (fetch in advance) when the page changes + * @param page The page number to fetch + * @param pageSize The number of items per page + * @param queryParams The optional query parameters to include in the request + * @param cacheSize The number of pages to cache in memory + * @param transform A function to transform the fetched data before returning it, called once after the request completes + * @returns The fetched data, undefined if the request has not completed + */ +export const useCachingApi = < + Endpoint extends QueryRequest>, + Return = Awaited> +>( + endpoint: Endpoint, + prefetch: number, + page: number, + pageSize: number, + queryParams?: Omit[0], "page" | "page_size">, + cacheSize: number = 20, + transform: (data: Awaited>) => Return = (data) => + data as Return +) => { + const [data, setData] = useState(undefined); + const [loading, setLoading] = useState(true); + const memoizedQueryParams = useQueryParamsMemo(queryParams ?? {}); + const memoizedEndpoint = useApiEndpointMemo( + endpoint as QueryRequest as MemoizableRequest + ); + const dataCache = useRef>>({}); + const lastUsed = useRef([]); + const loadingPromises = useRef>>( + new Map() + ); + + function cache(page: number, data: Return) { + if (lastUsed.current.length > cacheSize) { + delete dataCache.current[lastUsed.current.shift()!]; + } + dataCache.current[page] = data; + lastUsed.current = lastUsed.current.filter((p) => p !== page); + lastUsed.current.push(page); + return data; + } + + useEffect(() => { + // Update the last used list when the page changes + lastUsed.current = lastUsed.current.filter((p) => p !== page); + lastUsed.current.push(page); + }, [page]); + + useEffect(() => { + // Clear the cache if the endpoint or query params change, as these will invalidate the cache + console.log("Clearing cache"); + dataCache.current = {}; + lastUsed.current = []; + }, [memoizedEndpoint, memoizedQueryParams, pageSize]); + + /** + * Return the data for a given page, fetching it if necessary + * @param page The page number to fetch + * @returns The fetched data, undefined if the request has not completed + */ + async function fetchPage(page: number) { + if (page in dataCache.current) { + // Return the data from the cache if it exists + return dataCache.current[page]; + } else if (loadingPromises.current.has(page)) { + // Return the promise if it is actively loading + return loadingPromises.current.get(page); + } else { + // Fetch the data if it is not in the cache + const promise = memoizedEndpoint({ + ...memoizedQueryParams, + page: (page - 1) * pageSize, + page_size: pageSize, + }) + .then(transform) + .then(cache.bind(undefined, page)); + loadingPromises.current.set(page, promise); + return promise; + } + } + + function load() { + console.log( + "loading", + page, + pageSize, + memoizedQueryParams, + memoizedEndpoint + ); + const pagesToFetch = new Set(); + for (let i = page + 1; i < page + prefetch; i++) { + pagesToFetch.add(i); + } + if (page in dataCache.current) { + // If the data is in the cache, set it immediately + setData(dataCache.current[page]); + } else if (loadingPromises.current.has(page)) { + // If the data is actively being fetched, set loading to true and attach a then handler to set the data + setLoading(true); + loadingPromises.current + .get(page)! + .then(setData) + .finally(() => { + setLoading(false); + }); + } else { + // Only set loading to true if we can't immediately produce the data from the cache + // This prevents the loading spinner from flashing when the data is already in the cache + setLoading(true); + fetchPage(page).then((data) => { + setData(data); + setLoading(false); + }); + } + // Fetch the next pages in the background + Promise.all( + Array.from(pagesToFetch).map((page) => { + const promise = fetchPage(page).finally(() => { + loadingPromises.current.delete(page); + }); + loadingPromises.current.set(page, promise); + return promise; + }) + ).finally(() => { + // Just make sure that the currently-displayed page is the most recently used + lastUsed.current = lastUsed.current.filter((p) => p !== page); + lastUsed.current.push(page); + }); + } + + useEffect(() => { + load(); + }, [memoizedEndpoint, memoizedQueryParams, prefetch, page, pageSize]); + + type ReturnType = typeof loading extends true ? undefined : Return; + + return { + /** + * The fetched data, undefined if the request has not completed + */ + data, + /** + * True if the data to be displayed is still loading, false otherwise + */ + loading, + /** + * Perform an immediate reload of the data without modifying the query parameters. This also clears the cache. + */ + reload: useCallback(() => { + console.log("reloading"); + dataCache.current = {}; + lastUsed.current = []; + load(); + }, [memoizedEndpoint, memoizedQueryParams, page, pageSize]), + } as { + data: ReturnType; + loading: boolean; + reload: () => void; + }; +}; + +/** + * Fetch JSON data from a KCM API endpoint and allow the user to edit it. The data will be saved back to the API when the user clicks "Save". + * @param getEndpoint The API endpoint function to fetch data from. If null, the initial data object will be used instead. + * @param setEndpoint The API endpoint function to save data to + * @param initialData The initial data object to use if the fetch fails + * @param queryArgs The optional query parameters to include in the request + * @param transform A function to transform the fetched data before returning it, called once after the request completes. If the set endpoint + * expects a different data type than the get endpoint, this function is required in order to transform the data to the expected type. + * @returns + */ +export function useEditableData< + GetEndpoint extends QueryRequest | null, + SetEndpoint extends DataRequest, + Return = GetEndpoint extends null + ? Parameters[0] + : Awaited>> +>( + getEndpoint: GetEndpoint, + setEndpoint: SetEndpoint, + initialData: Return, + queryArgs?: Parameters[0], + transform: ( + data: ReturnType + ) => Return = (data) => data as Return +): { + /** + * The current data object, including any changes made by the user + */ + data: Return; + /** + * True if the data has been modified since the last save + */ + modified: boolean; + /** + * A function to create a new update function for a specific field + * @param field The field to update + * @returns A function to update the field + */ + update: ( + field: keyof Return + ) => Dispatch>; + /** + * Save the current data object to the API + * @returns A promise that resolves when the data has been saved + */ + save: () => Promise; + /** + * Perform an immediate reload of the data without modifying the query parameters. + * This will discard any unsaved changes. + */ + reload: () => void; + loading: boolean; +} { + const { + data: storedData, + loading, + reload, + } = useApiData(getEndpoint, queryArgs, transform); + const [data, setData] = useState(initialData); + const [modified, setModified] = useState(false); + + useEffect(() => { + setData(storedData ?? initialData); + setModified(false); + }, [storedData, objectHash(initialData ?? {})]); + + const update = useCallback( + (field: Field) => + (value: SetStateAction) => { + setModified(true); + setData((prev) => ({ + ...prev, + [field]: value instanceof Function ? value(prev[field]) : value, + })); + }, + [(setData as MemoizableRequest).endpoint] + ); + + const save = useCallback(async () => { + // The server will ignore any extra fields in the data object, so this is a safe cast + await setEndpoint(data); + setModified(false); + }, [data]); + + return { data, modified, update, save, reload, loading }; +} diff --git a/src/hooks/useCsrfToken.ts b/src/hooks/useCsrfToken.ts index dea9ce0..fe8dd41 100644 --- a/src/hooks/useCsrfToken.ts +++ b/src/hooks/useCsrfToken.ts @@ -8,8 +8,9 @@ export const useCsrfToken = () => { const { config } = useConfig(); useEffect(() => { if (!hasFetchedToken.current) { - getCSRFToken(config.apiUrl).then(token => { - setCsrfToken(token); + getCSRFToken(config.apiUrl).then(tokens => { + setCsrfToken(tokens.csrfToken); + // document.cookie = `csrftoken=${tokens.csrfToken}; path=/`; hasFetchedToken.current = true; }).catch(error => { console.error('Failed to fetch initial CSRF token:', error); diff --git a/src/types/api.ts b/src/types/api.ts new file mode 100644 index 0000000..0e95cfb --- /dev/null +++ b/src/types/api.ts @@ -0,0 +1,536 @@ +// Type definitions for the KCM API. +// Includes generic container types for API responses, as well as specific +// types for each endpoint. The specific types are accepted by POST requests, +// and the Response types are returned by GET requests and contain the ID field. +// PATCH-like requests take partial versions of the StoredObject generic +// types, and return the full Response type. + +import { KeyofStorable } from "."; +import { ISO8601String } from "../utilities/dateUtils"; +import * as Filters from "./apiFilters"; +export { Filters }; + +interface BaseListParams { + page_size?: number; + page?: number; +} + +/** + * Returned by an API endpoint that merely indicates a success or failure. Failure will + * result in an ApiError being thrown, so when the promise resolves, success is guaranteed + * to be true. + */ +export type GenericResponse = { + detail?: string; + message?: string; + success: true; +}; + +export type PaginatedApiFunction< + ResponseData, + Params extends PaginationParams = PaginationParams +> = (params: Params) => Promise>; + +/** + * Creates a type that represents the filters that can be passed to an advanced filtering endpoint + * that supports multiple "or" levels, based on the provided filter type indicating which fields are + * available for filtering. + */ +export type AdvancedFilterParams< + FilterType extends Partial< + Record, string | number | boolean> + > +> = { + [key in KeyofStorable as `${"~" | ""}${key}${| `{${number}}[${number}]` + | ""}`]?: FilterType[key]; + }; + +/** + * Utility type to strip advanced filter syntax from a set of parameters, returning the base filter type. + * If the parameters do not contain any advanced filter syntax, the original type is returned. + * If multiple levels of advanced filters are present, all levels are stripped. + * @template Params The type of the parameters to strip the advanced filter syntax from. + * @returns The base filter type. + */ +export type StripAdvancedFilters< + Params extends Partial< + Record, string | number | boolean> + > +> = Omit}{${number}}[${number}]`>; + +/** + * Represents the parameters that can be passed to a paginated list endpoint. + * @template Filters The type of the filters that can be passed to the endpoint. + */ +export type PaginationParams< + Filters extends Partial> = {} +> = Filters & BaseListParams; + +/** + * Represents the parameters that can be passed to a paginated list endpoint that + * supports results ordering. + * @template Filters The type of the filters that can be passed to the endpoint. + * @template Ordering The type of the ordering that can be passed to the endpoint. + */ +export type OrderablePaginationParams< + Filters extends Partial> = Partial< + Record + >, + Ordering extends string = any +> = PaginationParams & { + ordering?: Ordering | `-${Ordering}`; +}; + +/** + * Utility type to strip pagination parameters from a set of parameters, returning the base filter type. + * Keeps the `ordering` field if present, as it is not a pagination parameter. + */ +export type StripPaginationParams< + Params extends Partial< + Record, string | number | boolean> + > +> = Omit; +/** + * Represents the version of an object that is stored in the database. + * @template PostType The type of the object that is passed to the API in POST-like requests. + * @template AdditionalFields Any additional fields that are returned by the API that the POST-like requests do not accept, not including the ID field. + */ +export type StoredObject = Required & + AdditionalFields & { + /** + * The ID of the object. + */ + id: number; + }; + +/** + * Represents a response to a request that does not return any data. Used for standards-compliant + * DELETE-like requests. + */ +export type EmptyResponse = Promise; + +/** + * Represents a response to a request that directly returns a string. + */ +export type StringResponse = Promise; + +/** + * Represents a response to a request for a single object. + * @template PostType The type of the object that is passed to the API in POST-like requests. + * @template AdditionalFields Any additional fields that are returned by the API that the POST-like requests do not accept, not including the ID field. + */ +export type Response = Promise< + StoredObject +>; + +/** + * Represents a non-paginated response to a request for a list of objects. + * @template PostType The type of the object that is passed to the API in POST-like requests. + * @template AdditionalFields Any additional fields that are returned by the API that the POST-like requests do not accept, not including the ID field. + */ +export type ListResponse = Promise< + StoredObject[] +>; + +/** + * Represents a paginated response to a request for a list of objects. + * The `results` field is an array of StoredObjects of type `T`. + * @template PostType The type of the object that is passed to the API in POST-like requests. + * @template AdditionalFields Any additional fields that are returned by the API that the POST-like requests do not accept. + */ +export type PaginatedData = { + /** + * The total number of objects that match the query. + */ + count: number; + /** + * The URL of the next page of results, or null if there is no next page. + */ + next: string | null; + /** + * The URL of the previous page of results, or null if there is no previous page. + */ + previous: string | null; + /** + * The results of the query. + */ + results: StoredObject[]; +}; + +/** + * The data that can be passed to an update-like endpoint. + */ +export type PatchData = Partial< + StoredObject +>; + +export interface MultipleSelection { + /** + * The IDs of the objects to be operated on. + */ + selected: number[]; +} + +/** + * Interface for the User model. + */ +export interface User { + /** + * The username of the user. When a user is updating their own profile, this + * must match their current username. Changing the username is not supported. + */ + username: string; + /** + * The email address of the user. + */ + email: string; + /** + * The first name of the user. + */ + first_name: string; + /** + * The last name of the user. + */ + last_name: string; + /** + * Whether the user is active. When a user is updating their own profile, this + * field will be silently ignored. + */ + is_active: boolean; + /** + * Whether the user is a staff member. When a user is updating their own + * profile, this field will be silently ignored. + */ + is_staff: boolean; + /** + * Whether the user is a superuser. When a user is updating their own profile, + * this field will be silently ignored. + */ + is_superuser: boolean; + /** + * The date of the user's last login, in ISO format. + */ + last_login: string; + /** + * The date the user was created, in ISO format. + */ + date_joined: string; +} + +/** + * Interface for the Filter model. + */ +export interface SavedFilter { + /** + * The name of the filter. + */ + name: string; + /** + * The slug of the filter. This is a unique identifier for the filter. + */ + slug: string; + /** + * The parameters of the filter, in JSON format. + */ + json_string: string; +} + +export interface Device { + /** + * The hostname of the device. + */ + host: string; + /** + * The model of the device, if known. + */ + model: string | null; + /** + * The firmware version of the device, if known. + */ + version: string | null; + /** + * The vendor of the device. + */ + vendor: string; + /** + * The location of the device, if known. + */ + location: string | null; + /** + * The serial number of the device, if known. + */ + serial: string | null; + /** + * The asset tag of the device. + */ + asset: string; + /** + * Whether the device is reachable via SSH. + */ + ssh: boolean; + /** + * The firmware version loaded on the primary flash of the device, if known. + */ + primary_flash: string | null; + /** + * The firmware version loaded on the secondary flash of the device, if known. + */ + secondary_flash: string | null; + /** + * The IP address of the device, if any. + */ + ip: string | null; + /** + * Timestamp of the last successful update, in ISO format. + */ + lastupdate: string; + /** + * Whether the last update was successful. + */ + lastupdatesuccess: boolean; + /** + * The error message of the last update, if any. + */ + lastupdateerror: string | null; + /** + * The time of a pending scheduled reload, in ISO format. + * May be null if no reload is scheduled. + */ + reloadat: ISO8601String | null; +} + +/** + * Interface for the Job model. + */ +export interface Job { + /** + * The time the job is scheduled to run, in ISO format. + */ + scheduled_time: ISO8601String; + /** + * The command to run on the device. Multi-line string, should be + * rendered as a code block. + */ + command: string; + /** + * If true, the job will be run on devices concurrently. Defaults to true. + */ + concurrency?: boolean; + /** + * If true, devices will be rediscovered after the job is run. Defaults to + * false. + */ + rediscover?: boolean; + /** + * Integer timeout for the job. + */ + timeout: number; + /** + * The device IDs to run the job on. + */ + devices: number[]; +} + +export interface JobAdditionalFields { + /** + * The state of the job. A value of 0 indicates success, other values indicate + * errors. + */ + state: number; + /** + * The number of devices that succeeded in running the job. Only present on returned + * objects, do not pass this field to the API. + */ + success: number; + /** + * The number of devices that failed to run the job. Only present on returned + * objects, do not pass this field to the API. + */ + failed: number; +} + +/** + * Interface for the list websocket action's data array. + */ +export interface JobListData { + id: number; + scheduled_time: string; + progress: { + pending: number; + running: number; + failed: number; + success: number; + }; + state: number; + tasks: StoredObject[]; + devices: number[]; + url: string; +} + +/** + * Interface for the JobStatus model, which is read-only. + */ +export interface JobStatus { + /** + * The job start timestamp, in ISO format. May be null if the job has not + * started yet. + */ + start_time: string | null; + /** + * The job end timestamp, in ISO format. May be null if the job has not + * finished yet. + */ + end_time: string | null; + /** + * Whether the job was successful. Will be false if the job has not finished + * yet. + */ + success: boolean; + /** + * The hostname of the device the job was run on. + */ + host: string; + /** + * The output of the job. Multi-line string, should be rendered as a code + * block. + */ + output: string; + /** + * The ID of the device the job was run on. + */ + device_id: number; +} + +/** + * File list used by the firmware list endpoint. + */ +export interface FileList { + /** + * A list of files. Each file is a tuple of the filename and the name of the + * firmware. Also includes a special value for the "Select a File" option. + */ + files: ([string, string] | "" | "Select a File")[]; +} + +/** + * Interface for the Firmware model. + */ +export interface Firmware { + /** + * The filename of the firmware, relative to the firmware directory on the + * server. + */ + filename: string; + /** + * The name of the firmware, as displayed to the user. + */ + name: string; + /** + * Ordering of the firmware. Higher values are displayed first. + */ + ordering?: number; +} + +/** + * Interface for the FirmwareUpdate endpoint. + */ +export interface FirmwareUpdateArguments { + /** + * The IDs of the devices to update. + */ + devices: number[]; + /** + * The ID of the firmware to update to. + */ + firmware: number; + /** + * The time the update is scheduled to run, in ISO format. + */ + scheduled_time: string; +} + +/** + * Interface for the DiscoverGroup model. + */ +export interface DiscoverGroup { + /** + * The name of the OpenIPAM group this DiscoverGroup is associated with. + */ + ipamgroup: string; + /** + * The username to use when logging into devices. + */ + username: string; + /** + * The password to use when logging into devices. + */ + password: string; + /** + * The enable password to use when logging into devices. May be null. + */ + enablepassword: string | null; +} + +export interface ConfigDiff { + /** + * The diff string + */ + diff: string; + /** + * Added lines + */ + added: string[]; + /** + * Removed lines + */ + removed: string[]; +} + +export interface ConfigVersion { + /** + * The date the config was created, in ISO format. + */ + date_created: string; + /** + * The comment included with this revision. + */ + comment: string; + /** + * The configuration file. + */ + config: string; +} + +export interface LogEntry { + /** + * The ID of the job this log entry is associated with. + */ + job: number; + /** + * The ID of the device this log entry is associated with. + */ + device: number; + /** + * The timestamp of the log entry, in ISO format. + */ + stamp: string; + /** + * The severity of the log entry. + */ + severity: number; + /** + * The message of the log entry. + */ + message: string; +} + +export interface Interface { + description: string; + is_enabled: boolean; + is_operational: boolean; + name: string; + snmpid: number; + speed: number; + tagged: number[]; + untagged: number[]; +} diff --git a/src/types/apiFilters.ts b/src/types/apiFilters.ts new file mode 100644 index 0000000..04c87aa --- /dev/null +++ b/src/types/apiFilters.ts @@ -0,0 +1,130 @@ +// Filter interfaces for API list endpoints + +import { AdvancedFilterParams } from "./api"; + +export enum PythonBoolString { + true = "True", + false = "False", +} + +/** + * Serialize a boolean value to a Python boolean string, since our API is Python-based. + * This should only be necessary for query parameters, as any body data will be JSON. + * @param value The boolean value to serialize + * @returns The Python boolean string (True or False) + */ +export function serializeBoolean(value: boolean): PythonBoolString { + return value ? PythonBoolString.true : PythonBoolString.false; +} + +export type JobFilter = Partial<{ + id: string; + scheduled_time__gte: string; + scheduled_time__lte: string; + command: string; + device: number; +}>; + +export type JobStatusFilter = Partial<{ + job: number; + success: boolean; +}>; + +export type FirmwareFilter = Partial<{ + id: number; + filename: string; + name: string; +}>; + +export type DiscoverGroupFilter = Partial<{ + id: number; + ipamgroup: string; + username: string; +}>; + +export enum DeviceOrdering { + ID = "id", + Host = "host", + Model = "model", + Version = "version", + IP = "ip", + Vendor = "vendor", + Location = "location", + PrimaryFlash = "primary_flash", + LastUpdateSuccess = "lastupdatesuccess", + LastUpdateError = "lastupdateerror", + LastUpdate = "lastupdate", + DeviceConfig = "device_config", + Serial = "serial", + SSH = "ssh", + SecondaryFlash = "secondary_flash", +} + +export enum DeviceVendor { + Aruba = "Aruba", + Brocade = "Brocade/Foundry", + HPE = "HPE/Aruba", + Juniper = "Juniper Networks", + Ubiquiti = "Ubiquiti airOS", + Unknown = "unknown (failed discovery)", +} + +export type DeviceFilter = AdvancedFilterParams< + Partial<{ + id: string; + host: string; + model: string; + version: string; + ip: string; + vendor: DeviceVendor; + location: string; + primary_flash: string; + lastupdatesuccess: boolean; + lastupdateerror: string; + lastupdate__gte: string; + lastupdate__lte: string; + device_config: string; + serial: string; + }> +>; + +export type LogFilter = Partial<{ + device: number; + severity: number; + message: string; + job: string; + stamp__gte: string; + stamp__lte: string; +}>; + +export type InterfaceFilter = Partial<{ + /** + * The ID of the device to filter interfaces by. + */ + device: number; + /** + * The SNMP ID of the interface. + */ + snmpid: string; + /** + * The name of the interface. + */ + name: string; + /** + * The description of the interface. + */ + description: string; + /** + * The link speed of the interface. + */ + speed: number; +}>; + +export enum InterfaceOrdering { + SNMPID = "snmpid", + Name = "name", + Description = "description", + Speed = "speed", + IsEnabled = "is_enabled", + IsOperational = "is_operational", +} diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 0000000..b9c7cee --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1,65 @@ +/** + * Storable objects are objects that can be stored in the database. They can only + * have string keys. Trying to enforce this at the type level causes a lot of + * issues, namely the inability to use the `unknown` type in generic constraints. + * This type is therefore used whenever we need to reference the keys of a storable + * object. + */ +export type KeyofStorable = string & keyof T; + +export type Columns = { + [key: string]: Column; +}; + +export type Column = { + header?: string; + baseWidth?: string; + hidden?: boolean; + tdClass?: string; + filter?: { + type: string; + placeholder?: string; + options?: string[]; + values?: string[]; + render?: ({ + params, + negate, + paramKey, + setParam, + delParam, + }: { + params: Params; + negate: boolean; + paramKey: string; + setParam: Function | any; + delParam: Function; + }) => React.ReactNode; + }; + orderable?: boolean; + render?: + | (( + value: any, + options?: any, + list?: any, + params?: Params + ) => React.ReactNode | string) + | string; + [key: string]: any; +}; + +export type Params = { + page_size: number; + page: number; + ordering?: string; + selected?: (string | number)[]; + [key: string]: any; +}; + +export type Filter = { + name: string; + params: Params; + json_string: string; + id: number; +}; + +export * as API from "./api"; diff --git a/src/utilities/apiFunctions.ts b/src/utilities/apiFunctions.ts index dd61c2d..386cbbe 100644 --- a/src/utilities/apiFunctions.ts +++ b/src/utilities/apiFunctions.ts @@ -1,16 +1,45 @@ -import { useToken } from "../hooks/useToken"; +import { API } from "../types"; +import { serializeBoolean } from "../types/apiFilters"; +import { getCookie } from "./getCookie"; -export const getApiEndpointFunctions = () => ({ - /** - * Logs API - */ +/** + * This is a hook that provides a simple interface for making requests to the + * API. + * Each function returned by this hook can throw two types of errors: + * - `ApiError`: This error is thrown when the API returns a non-200 response. + * The `status` property of this error contains the status code returned by + * the API, and the `message` property contains any error message returned. + * - `ApiResponseError`: This error is thrown when the API returns a 200 + * response, but the response body is not what was expected. It contains the raw + * `Response` object returned by the `fetch` call. This can happen for, at + * minimum, any of the following reasons: + * - The response body is empty, but the request was expected to return data (e.g. not a 204 response) + * - The response body is not valid JSON, but the request was expected to return JSON + * - The response body is not valid text, but the request was expected to return text + * + * API calls which are expected to return a 204 response will have a return type of `void`. + * However, as it is determined by the server whether or not to return a 204 + * response, it is possible for an API call to return a 204 response unexpectedly. + * For this reason, it may be a good idea to check the result data for `undefined` before using it. + * To enforce this, call this hook with a template parameter of `void`. + */ +export const getApiEndpointFunctions = < + StrictTypeChecking extends void | never = never +>() => ({ logs: { - get: requestGenerator(HttpMethod.GET, "logs"), - getEmails: requestGenerator(HttpMethod.GET, "logs/email"), + get: requestGenerator< + HttpMethod.GET, + API.PaginationParams, + API.PaginatedData | StrictTypeChecking + >(HttpMethod.GET, "logs/", { headers: { "Content-Type": "application/json", 'X-CSRFToken': getCookie('csrftoken') ?? "" } }), }, -}) +}); -const BASE_URL = import.meta.env.VITE_API_URL +declare global { + var api: ReturnType; +} + +const BASE_URL = "http://127.0.0.1:8000/api/v2"; enum HttpMethod { GET = "GET", @@ -20,27 +49,241 @@ enum HttpMethod { PATCH = "PATCH", } -function requestGenerator(method: string, url: string, base?: string) { - url = `${base ?? BASE_URL}/${url}`; - const token = useToken(); +export class ApiError extends Error { + constructor(message: string, public readonly status: number) { + super(message); + } +} + +export class ApiResponseError extends ApiError { + constructor(public readonly response: Response) { + super( + "API did not return a valid JSON response. See response property.", + response.status + ); + } +} + +/** + * JSON fields that the API uses to describe errors. Should check all of these + * when attempting to parse an error response. The last field in this list + * found in the response will be used as the error message. + */ +const ERROR_DESCRIPTION_FIELDS = ["detail"]; + +/** + * Handles the response from the API. + * + * @param response The response from the API + * @returns The parsed JSON response + * @throws ApiError if the response is not ok + * @throws ApiResponseError if the response is ok but the JSON is invalid + */ +async function handleResponse( + response: Response, + asText: boolean +): Promise { + if (response.ok) { + try { + if (asText) { + // @ts-ignore Accounted for by the `asText` parameter, and the template parameters + // that chain back to this function are only accessible within this file + return await response.text(); + } else { + return await response.json(); + } + } catch (e) { + // Server returned a 200 but the response was not valid + if (response.status === 204) { + // If the response was supposed to be empty, return null + return undefined; + } + throw new ApiResponseError(response.clone()); + } + } else { + return response.json().then( + (data) => { + const message = ERROR_DESCRIPTION_FIELDS.reduce( + (acc, field) => { + if (data[field]) { + return data[field]; + } + return acc; + }, + // Default to the status text if no error description fields were found + response.statusText + ); + throw new ApiError(message, response.status); + }, + () => { + throw new ApiError(response.statusText, response.status); + } + ); + } +} + +export type QueryRequest = ( + params?: ParamsType, + extraHeaders?: Record, + controller?: AbortController +) => Promise; +export type DataRequest = ( + data?: DataType, + extraHeaders?: Record, + controller?: AbortController +) => Promise; + +/** + * A request that can be memoized. This is the actual type returned by the + * `requestGenerator` function, but it is not declared as such in order to + * preserve syntax highlighting. It is only needed when memoizing the request + * functions (e.g. in the `useApiData` hook). When doing so, cast the request + * function to this type. This is a safe cast. + */ +export type MemoizableRequest = (QueryRequest | DataRequest) & { + endpoint: string; +}; + +/** + * Generates a function that can be used to make requests to a specific API endpoint. + * @template Method The HTTP method to use for these requests. Required for type inference. + * @template DataType The type of data that this request expects to receive in the request body, or as query parameters. Void if no data is expected. + * @template ResponseType The type of data that this request returns in the response body when successful. + * @param method The HTTP method to use for these requests. Should be the same as the Method template parameter. + * @param url The URL to send the request to + * @param extra Any additional options to pass to the request generator + * @returns A function that can be used to make requests to the API with the given parameters + */ +function requestGenerator< + Method extends HttpMethod, + DataType = void, + ResponseType = void +>( + method: Method, + url: string, + extra: { + /** + * Any additional headers to send with these requests. + */ + headers?: Record; + /** + * The base URL to use for these requests. Defaults to `/api`. + */ + base?: string; + /** + * Whether or not to parse the response as text instead of JSON. + */ + text?: boolean; + /** + * Whether or not this request is form data. + */ + form?: boolean; + /** + * Whether or not to return the raw response instead of parsing it. + */ + raw?: boolean; + } = {} +): Method extends HttpMethod.GET + ? QueryRequest + : DataRequest { + const { + headers = {}, + base = BASE_URL, + text = false, + form = false, + raw = false, + } = extra; + url = `${base}/${url}`; switch (method) { case "GET": - return async (params?: { [key: string]: any }) => { - const query = new URLSearchParams(params ?? {}).toString(); - const response = await fetch(`${url}?${query}`); - return response.json(); + // TODO: add params type and update code that uses this + /** + * Call this API endpoint with the given query parameters. + * @param params The query parameters to send in the request + * @param extraHeaders Any additional headers to send in the request + * @param controller An optional AbortController to use for the request + * @returns The parsed JSON response + * @throws ApiError if the response is not ok + * @throws ApiResponseError if the response is ok but the JSON is invalid + */ + const queryRequest = async ( + params?: DataType, + extraHeaders: Record = {}, + controller?: AbortController + ) => { + const newParams = Object.entries(params ?? {}).reduce( + (acc, [key, value]) => { + if (typeof value === "boolean") { + acc[key] = serializeBoolean(value); + } else { + // serialize undefined as None (null in Python) + acc[key] = String(value ?? "None"); + } + return acc; + }, + {} as Record + ); + const query = new URLSearchParams(newParams).toString(); + const response = await fetch(`${url}?${query}`, { + method, + headers: { + ...headers, + ...extraHeaders, + }, + signal: controller?.signal, + credentials: "include", + }); + // NOTE: the `any` type here does somewhat break type safety, but it's + // necessary to allow the return type to vary based on the passed in + // parameters. This is only used internally, so it's not a huge deal. + // The main concern is an unexpected 204 response. If that happens, the + // response will be null, which will trigger a type error if the + // response is used as a non-nullable type. Any other empty response + // will trigger an `ApiResponseError` in the `handleResponse` function, + // which is expected behavior. + if (raw) return response as any; + return handleResponse(response, text); }; + return Object.assign(queryRequest, { endpoint: url }); default: - return async (data?: { [key: string]: any }) => { + /** + * Call this API endpoint with the given data in the request body. + * @param data The data to send in the request body + * @param extraHeaders Any additional headers to send in the request + * @param controller An optional AbortController to use for the request + * @returns The parsed JSON response + * @throws ApiError if the response is not ok + * @throws ApiResponseError if the response is ok but the JSON is invalid + */ + const dataRequest = async ( + data?: DataType, + extraHeaders: Record = {}, + controller?: AbortController + ) => { + const token = getCookie("csrftoken"); + if (token === undefined) { + throw new Error("CSRF token not found"); + } + if (!form && !("Content-Type" in headers)) { + headers["Content-Type"] = "application/json"; + } const response = await fetch(url, { method, headers: { - "Content-Type": "application/json", - "X-CSRFToken": token ?? "", + ...headers, + ...extraHeaders, + // Add the CSRF token to the headers, overriding any value that + // might have been passed in the extra headers + "X-CSRFToken": token, }, - body: JSON.stringify(data ?? {}), + signal: controller?.signal, + body: form ? (data as FormData) : JSON.stringify(data), }); - return response.json(); + if (raw) return response as any; + return handleResponse(response, text); }; + return Object.assign(dataRequest, { endpoint: url }); } } + +window.api = getApiEndpointFunctions(); diff --git a/src/utilities/dateUtils.ts b/src/utilities/dateUtils.ts new file mode 100644 index 0000000..fa08244 --- /dev/null +++ b/src/utilities/dateUtils.ts @@ -0,0 +1,49 @@ +/** + * A string representing a date in the ISO 8601 format. + */ +export type ISO8601String = `${number}-${number}-${number}T${number}:${number}${| `:${number}${`.${number}` | ""}` + | ""}${"Z" | `${"+" | "-"}${number}:${number}`}`; + +/** + * A string representing a date in the ISO 8601 format in the UTC timezone. This is the same as the output of `Date.prototype.toISOString`. + */ +export type ISOUTCString = `${number}-${number}-${number}T${number}:${number}${| `:${number}${`.${number}` | ""}` + | ""}Z`; + +declare global { + interface Date { + /** + * Returns a date as a string value in ISO 8601 format + */ + toLocalISOString(): ISO8601String; + + /** + * Returns a date as a string value in ISO 8601 format in the UTC timezone + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString + */ + toUTCISOString(): ISOUTCString; + + /** + * Returns a date as a string value in ISO 8601 format in the UTC timezone + * @deprecated Use `toLocalISOString` or `toUTCISOString` instead to be explicit about the intended timezone + * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString + */ + toISOString(): ISOUTCString; + } +} + +Date.prototype.toLocalISOString = function () { + const offset = this.getTimezoneOffset(); + const localTime = new Date(this.getTime() - offset * 60 * 1000); + const hourOffset = Math.floor(Math.abs(offset) / 60); + const minuteOffset = Math.abs(offset) % 60; + + const sign = offset < 0 ? "+" : "-"; + const hour = hourOffset.toString().padStart(2, "0"); + const minute = minuteOffset.toString().padStart(2, "0"); + return localTime + .toUTCISOString() + .replace("Z", `${sign}${hour}:${minute}`) as ISO8601String; +}; + +Date.prototype.toUTCISOString = Date.prototype.toISOString; diff --git a/src/utilities/getCookie.ts b/src/utilities/getCookie.ts new file mode 100644 index 0000000..43de532 --- /dev/null +++ b/src/utilities/getCookie.ts @@ -0,0 +1,14 @@ +/** + * Gets a cookie from `document.cookie` + * + * @param {string} name + * @returns first cookie value or undefined if no cookie is found + */ +export const getCookie = (name: string): string | void => { + const cookie = document.cookie + .split(";") + .map((v) => v.split("=").map((v) => v.trim())) + .find(([k]) => k === name); + if (cookie) return decodeURIComponent(cookie[1]); + else return "asdf"; +}; diff --git a/yarn.lock b/yarn.lock index e149fff..a094927 100644 --- a/yarn.lock +++ b/yarn.lock @@ -323,6 +323,11 @@ dependencies: undici-types "~5.26.4" +"@types/object-hash@^3.0.6": + version "3.0.6" + resolved "https://registry.npmjs.org/@types/object-hash/-/object-hash-3.0.6.tgz" + integrity sha512-fOBV8C1FIu2ELinoILQ+ApxcUKz4ngq+IWUYrxSGjXzzjUALijilampwkMgEtJ+h2njAW3pi853QpzNVCHB73w== + "@types/prop-types@*": version "15.7.12" resolved "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz" @@ -1179,6 +1184,11 @@ object-assign@^4.1.1: resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz" integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== +object-hash@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz" + integrity sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw== + once@^1.3.0: version "1.4.0" resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz" From ec1b42f11f049eb676309efb459af6944a14bfe3 Mon Sep 17 00:00:00 2001 From: Treyson Date: Mon, 15 Jul 2024 13:06:17 -0600 Subject: [PATCH 10/96] setup page system, made home dashboard and started implementing certain aspects --- src/App.tsx | 6 +- src/components/Home.tsx | 29 +++++++- src/components/{Login.tsx => LoginForm.tsx} | 3 +- src/components/Logslist.tsx | 5 -- src/components/dashboard/Actions.tsx | 81 +++++++++++++++++++++ src/components/dashboard/Admin.tsx | 15 ++++ src/components/dashboard/Navigation.tsx | 15 ++++ src/components/dashboard/Stats.tsx | 55 ++++++++++++++ src/components/dashboard/Welcome.tsx | 54 ++++++++++++++ src/components/dashboard/index.tsx | 28 +++++++ src/hooks/useCsrfToken.ts | 29 +++++--- src/pages/Home.tsx | 12 +++ src/pages/Login.tsx | 12 +++ src/types/api.ts | 9 +++ src/types/apiFilters.ts | 9 +++ src/utilities/apiFunctions.ts | 18 ++++- src/utilities/getCookie.ts | 6 ++ 17 files changed, 360 insertions(+), 26 deletions(-) rename src/components/{Login.tsx => LoginForm.tsx} (96%) delete mode 100644 src/components/Logslist.tsx create mode 100644 src/components/dashboard/Actions.tsx create mode 100644 src/components/dashboard/Admin.tsx create mode 100644 src/components/dashboard/Navigation.tsx create mode 100644 src/components/dashboard/Stats.tsx create mode 100644 src/components/dashboard/Welcome.tsx create mode 100644 src/components/dashboard/index.tsx create mode 100644 src/pages/Home.tsx create mode 100644 src/pages/Login.tsx diff --git a/src/App.tsx b/src/App.tsx index 5ea208e..5da0992 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,7 +1,8 @@ import "@mantine/core/styles.css"; import { MantineProvider } from "@mantine/core"; -import Home from "./components/Home"; -import Login from "./components/Login"; +import Home from "./pages/Home"; +import Login from "./pages/Login"; +import Demo from "./components/Home" import { BrowserRouter, Routes, Route } from "react-router-dom"; import config from "./config"; import { ConfigProvider } from "./contexts/ConfigContext"; @@ -15,6 +16,7 @@ function App() { } /> } /> + } /> diff --git a/src/components/Home.tsx b/src/components/Home.tsx index 1bd0ef9..2e589bc 100644 --- a/src/components/Home.tsx +++ b/src/components/Home.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from 'react'; +import { useState, useEffect, useCallback } from 'react'; import { Button, Text, Group, Container, Paper, Title } from '@mantine/core'; import { useNavigate } from 'react-router-dom'; import { useConfig } from '../contexts/ConfigContext'; @@ -6,17 +6,18 @@ import { apiCall } from '../api'; import { useCsrfToken } from '../hooks/useCsrfToken'; import { getApiEndpointFunctions } from '../utilities/apiFunctions'; import { usePaginatedApi } from '../hooks/useApi'; +import { getCookie, setCookie } from '../utilities/getCookie'; const Home = (): JSX.Element => { const [page, setPage] = useState(1); const [data, setData] = useState([]); const navigate = useNavigate(); const { config } = useConfig(); - const csrftoken = useCsrfToken(); + const { csrftoken, fetchCsrfToken } = useCsrfToken(); const [userName, setUserName] = useState(null); const api = getApiEndpointFunctions(); - const { data: paginatedData, loading, reload } = usePaginatedApi(api.logs.get, page, 10); + const { data: paginatedData, loading, reload } = usePaginatedApi(api.logs.get, page, 5); useEffect(() => { if (paginatedData && paginatedData.results) { @@ -28,10 +29,28 @@ const Home = (): JSX.Element => { setPage(page + 1); }; + function updateCSRFToken(newToken: string) { + const csrfCookieName = 'csrftoken'; + const currentToken = getCookie(csrfCookieName); + + if (currentToken !== newToken) { + setCookie(csrfCookieName, newToken); + console.log("set new token") + } + } + + const logout = useCallback(async () => { + await fetchCsrfToken(); + updateCSRFToken(csrftoken); + await api.auth.logout(); + await reload(); + }, [reload]); + const handleLogout = async () => { try { + await fetchCsrfToken(); const url = `${config.apiUrl}/logout/`; const data = await apiCall(url, 'POST', null, csrftoken); console.log(data); @@ -41,6 +60,7 @@ const Home = (): JSX.Element => { } }; + const handleWhoAmI = async () => { try { const url = `${config.apiUrl}/whoami/`; @@ -59,6 +79,7 @@ const Home = (): JSX.Element => { handleWhoAmI(); }, []); + return ( @@ -82,7 +103,7 @@ const Home = (): JSX.Element => { nextpage {data && data.map((item: any) => ( -
+
ID: {item.id} Content Type: {item.content_type} Action Flag: {item.action_flag} diff --git a/src/components/Login.tsx b/src/components/LoginForm.tsx similarity index 96% rename from src/components/Login.tsx rename to src/components/LoginForm.tsx index 8f783c7..0456a69 100644 --- a/src/components/Login.tsx +++ b/src/components/LoginForm.tsx @@ -12,10 +12,11 @@ const LoginForm = () => { const navigate = useNavigate(); const { config } = useConfig(); - const csrftoken = useCsrfToken(); + const { csrftoken, fetchCsrfToken } = useCsrfToken(); const handleLogin = async () => { try { + await fetchCsrfToken(); const url = `${config.apiUrl}/login/`; const data = await apiCall(url, 'POST', { username, password }, csrftoken); console.log('Login successful:', data); diff --git a/src/components/Logslist.tsx b/src/components/Logslist.tsx deleted file mode 100644 index af5cfae..0000000 --- a/src/components/Logslist.tsx +++ /dev/null @@ -1,5 +0,0 @@ -const LogsList = () => { - -} - -export default LogsList; \ No newline at end of file diff --git a/src/components/dashboard/Actions.tsx b/src/components/dashboard/Actions.tsx new file mode 100644 index 0000000..cff31e2 --- /dev/null +++ b/src/components/dashboard/Actions.tsx @@ -0,0 +1,81 @@ +import { useState, useEffect } from 'react'; +import { Button, Text, Group, Container, Paper, Title, Table } from '@mantine/core'; +import { useNavigate } from 'react-router-dom'; +import { useConfig } from '../../contexts/ConfigContext'; +import { apiCall } from '../../api'; +import { useCsrfToken } from '../../hooks/useCsrfToken'; +import { getApiEndpointFunctions } from '../../utilities/apiFunctions'; +import { usePaginatedApi } from '../../hooks/useApi'; + + +const Actions = (): JSX.Element => { + const [page, setPage] = useState(1); + const [data, setData] = useState([]); + const navigate = useNavigate(); + const { csrftoken, fetchCsrfToken } = useCsrfToken(); + const { config } = useConfig(); + const [userName, setUserName] = useState(null); + const api = getApiEndpointFunctions(); + + const { data: paginatedData, loading, reload } = usePaginatedApi(api.logs.get, page, 5); + + useEffect(() => { + if (paginatedData && paginatedData.results) { + setData(paginatedData.results); + } + }, [paginatedData]); + + const nextPage = async () => { + setPage(page + 1); + }; + + const handleWhoAmI = async () => { + try { + const url = `${config.apiUrl}/whoami/`; + const data = await apiCall(url, 'GET', null, csrftoken); + if (data.user === null) { + navigate('/login'); + return; + } + setUserName(data.user.username); + } catch (error) { + console.error('There was a problem with the fetch operation:', error); + } + }; + + useEffect(() => { + handleWhoAmI(); + }, []); + + return ( + + + Recent Actions + + + {data && data.map((item: any) => { + const date = new Date(item.action_time); + const options: Intl.DateTimeFormatOptions = { year: 'numeric', month: 'long', day: 'numeric' }; + const formattedDate = date.toLocaleDateString('en-US', options); + + return ( + + + {item.content_type} {item.object_repr} + + + {formattedDate} + + + ); + })} + +
+
+
+ ); + + +} + +export default Actions; \ No newline at end of file diff --git a/src/components/dashboard/Admin.tsx b/src/components/dashboard/Admin.tsx new file mode 100644 index 0000000..80d5cd6 --- /dev/null +++ b/src/components/dashboard/Admin.tsx @@ -0,0 +1,15 @@ +import { Text, Container, Paper, Title } from '@mantine/core'; + + +const Admin = () => { + return ( + + + Admin + This is broken on my prod, all the links lead to the home page ;/ + + + ); +}; + +export default Admin; \ No newline at end of file diff --git a/src/components/dashboard/Navigation.tsx b/src/components/dashboard/Navigation.tsx new file mode 100644 index 0000000..f710359 --- /dev/null +++ b/src/components/dashboard/Navigation.tsx @@ -0,0 +1,15 @@ +import { Text, Container, Paper, Title } from '@mantine/core'; + + +const Navigation = () => { + return ( + + + Navigation + Text + + + ); +}; + +export default Navigation; \ No newline at end of file diff --git a/src/components/dashboard/Stats.tsx b/src/components/dashboard/Stats.tsx new file mode 100644 index 0000000..9d00e53 --- /dev/null +++ b/src/components/dashboard/Stats.tsx @@ -0,0 +1,55 @@ +import { useEffect, useState } from 'react'; +import { useApiData } from '../../hooks/useApi'; +import { Container, Paper, Title } from '@mantine/core'; + +const Stats = () => { + const [data, setData] = useState([]); + const { data: apiData, loading, error } = useApiData(api.reports.recent); + const hostItems = ["hosts_today", "hosts_week", "hosts_month"]; + const userItems = ["users_today", "users_week", "users_month"]; + + const itemText = (item: string) => + item.startsWith("hosts") ? item.replace("hosts_", item.includes("today") ? "Hosts changed " : "Hosts changed this ") : + item.startsWith("users") ? item.replace("users_", item.includes("today") ? "Users joined " : "Users joined this ") : + item; + + + useEffect(() => { + if (apiData) { + setData(apiData); + } + }, [apiData]); + + return ( + + + Stats + + {loading &&

Loading...

} + {error &&

Error: {error.message}

} + {data && ( + <> + + Hosts + {hostItems.map((item) => ( +

+ {itemText(item)}: {data[item]} +

+ ))} +
+ + Users + {userItems.map((item) => ( +

+ {data[item]} {itemText(item)} +

+ ))} +
+ + )} +
+
+ ); +}; + +export default Stats; \ No newline at end of file diff --git a/src/components/dashboard/Welcome.tsx b/src/components/dashboard/Welcome.tsx new file mode 100644 index 0000000..6bada82 --- /dev/null +++ b/src/components/dashboard/Welcome.tsx @@ -0,0 +1,54 @@ +import { Text, Container, Paper, Title } from '@mantine/core'; +import { useEffect, useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { useConfig } from '../../contexts/ConfigContext'; +import { apiCall } from '../../api'; +import { useCsrfToken } from '../../hooks/useCsrfToken'; + + + +const Welcome = () => { + // idek if we want username compatability, cuz sometimes is just an Anumber + const { config } = useConfig(); + const { csrftoken } = useCsrfToken(); + const navigate = useNavigate(); + const [userName, setUserName] = useState(null); + + const handleWhoAmI = async () => { + try { + const url = `${config.apiUrl}/whoami/`; + const data = await apiCall(url, 'GET', null, csrftoken); + if (data.user === null) { + navigate('/login'); + return; + } + const user = data.user.username; + setUserName(user.charAt(0).toUpperCase() + user.slice(1)); + } catch (error) { + console.error('There was a problem with the fetch operation:', error); + } + }; + + useEffect(() => { + handleWhoAmI(); + }, []); + + return ( + + + Welcome to openIPAM, {userName} + Intro para +

+ We are now using Issues on GitHub to help aid us with features and bugs. Please make an issue on GitHub to give us feedback. + Item to consider when using the new interface: + Permissions - Do you have all your permissions? + Hosts - Do you see all your hosts? + DNS Entries - Do you see all DNS Entries? + If you have any questions, please email: openipam@lists.usu.edu +

+
+
+ ); +}; + +export default Welcome; \ No newline at end of file diff --git a/src/components/dashboard/index.tsx b/src/components/dashboard/index.tsx new file mode 100644 index 0000000..f13c6e1 --- /dev/null +++ b/src/components/dashboard/index.tsx @@ -0,0 +1,28 @@ +import Actions from "./Actions"; +import Admin from "./Admin"; +import Navigation from "./Navigation"; +import Stats from "./Stats"; +import Welcome from "./Welcome"; + + +import { Grid } from '@mantine/core'; + +const DashBoard = () => { + return ( + <> + + + + + + + + + + + + + ); +} + +export default DashBoard; \ No newline at end of file diff --git a/src/hooks/useCsrfToken.ts b/src/hooks/useCsrfToken.ts index fe8dd41..28a7bed 100644 --- a/src/hooks/useCsrfToken.ts +++ b/src/hooks/useCsrfToken.ts @@ -1,4 +1,4 @@ -import { useEffect, useRef, useState } from 'react'; +import { useEffect, useRef, useState, useCallback } from 'react'; import { getCSRFToken } from '../api'; import { useConfig } from '../contexts/ConfigContext'; @@ -6,17 +6,22 @@ export const useCsrfToken = () => { const [csrftoken, setCsrfToken] = useState(''); const hasFetchedToken = useRef(false); const { config } = useConfig(); - useEffect(() => { - if (!hasFetchedToken.current) { - getCSRFToken(config.apiUrl).then(tokens => { - setCsrfToken(tokens.csrfToken); - // document.cookie = `csrftoken=${tokens.csrfToken}; path=/`; - hasFetchedToken.current = true; - }).catch(error => { - console.error('Failed to fetch initial CSRF token:', error); - }); + + const fetchCsrfToken = useCallback(async () => { + try { + const tokens = await getCSRFToken(config.apiUrl); + setCsrfToken(tokens.csrfToken); + hasFetchedToken.current = true; + } catch (error) { + console.error('Failed to fetch CSRF token:', error); } - }, []); + }, [config.apiUrl]); + + useEffect(() => { + // if (!hasFetchedToken.current) { + fetchCsrfToken(); + // } + }, [fetchCsrfToken]); - return csrftoken; + return { csrftoken, fetchCsrfToken }; }; diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx new file mode 100644 index 0000000..299b4e0 --- /dev/null +++ b/src/pages/Home.tsx @@ -0,0 +1,12 @@ +import DashBoard from '../components/dashboard'; + + +const Home = () => { + return ( + <> + + + ); +}; + +export default Home; \ No newline at end of file diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx new file mode 100644 index 0000000..4f064e0 --- /dev/null +++ b/src/pages/Login.tsx @@ -0,0 +1,12 @@ +import LoginForm from "../components/LoginForm"; + + +const Login = () => { + return ( +
+ +
+ ); +}; + +export default Login; \ No newline at end of file diff --git a/src/types/api.ts b/src/types/api.ts index 0e95cfb..69ac748 100644 --- a/src/types/api.ts +++ b/src/types/api.ts @@ -524,6 +524,15 @@ export interface LogEntry { message: string; } +export interface RecentReport { + hosts_today: number; + hosts_week: number; + hosts_month: number; + users_today: number; + users_week: number; + users_month: number; +} + export interface Interface { description: string; is_enabled: boolean; diff --git a/src/types/apiFilters.ts b/src/types/apiFilters.ts index 04c87aa..953bf05 100644 --- a/src/types/apiFilters.ts +++ b/src/types/apiFilters.ts @@ -97,6 +97,15 @@ export type LogFilter = Partial<{ stamp__lte: string; }>; +export type RecentReportFilter = Partial<{ + hosts_today: number; + hosts_week: number; + hosts_month: number; + users_today: number; + users_week: number; + users_month: number; +}> + export type InterfaceFilter = Partial<{ /** * The ID of the device to filter interfaces by. diff --git a/src/utilities/apiFunctions.ts b/src/utilities/apiFunctions.ts index 386cbbe..dbf3e04 100644 --- a/src/utilities/apiFunctions.ts +++ b/src/utilities/apiFunctions.ts @@ -26,13 +26,27 @@ import { getCookie } from "./getCookie"; export const getApiEndpointFunctions = < StrictTypeChecking extends void | never = never >() => ({ + auth: { + logout: requestGenerator< + HttpMethod.POST, + void, + API.GenericResponse | StrictTypeChecking + >(HttpMethod.POST, "logout/", { headers: { 'Content-Type': 'application/json', 'X-CSRFToken': getCookie("csrftoken") ?? "" } }), + }, logs: { get: requestGenerator< HttpMethod.GET, API.PaginationParams, API.PaginatedData | StrictTypeChecking - >(HttpMethod.GET, "logs/", { headers: { "Content-Type": "application/json", 'X-CSRFToken': getCookie('csrftoken') ?? "" } }), + >(HttpMethod.GET, "logs/", { headers: { "Content-Type": "application/json" } }), }, + reports: { + recent: requestGenerator< + HttpMethod.GET, + Array, + API.RecentReport | StrictTypeChecking + >(HttpMethod.GET, "report/recent-stats", { headers: { "Content-Type": "application/json" } }), + } }); declare global { @@ -274,10 +288,10 @@ function requestGenerator< ...extraHeaders, // Add the CSRF token to the headers, overriding any value that // might have been passed in the extra headers - "X-CSRFToken": token, }, signal: controller?.signal, body: form ? (data as FormData) : JSON.stringify(data), + credentials: "include", }); if (raw) return response as any; return handleResponse(response, text); diff --git a/src/utilities/getCookie.ts b/src/utilities/getCookie.ts index 43de532..f767daa 100644 --- a/src/utilities/getCookie.ts +++ b/src/utilities/getCookie.ts @@ -12,3 +12,9 @@ export const getCookie = (name: string): string | void => { if (cookie) return decodeURIComponent(cookie[1]); else return "asdf"; }; + +export const setCookie = (name: string, value: string, days = 1) => { + const date = new Date(); + date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000); + document.cookie = `${name}=${value};expires=${date.toUTCString()};path=/`; +} \ No newline at end of file From 8a6005b6c849b3da81394de95a74920831d41cb1 Mon Sep 17 00:00:00 2001 From: Treyson Date: Tue, 16 Jul 2024 16:03:35 -0600 Subject: [PATCH 11/96] pagination component is going well, forgot to commit for a while --- package-lock.json | 695 +++++++++++++++++- package.json | 2 + src/App.tsx | 33 +- src/api.ts | 1 - src/components/Header.tsx | 55 ++ src/components/Home.tsx | 36 +- src/components/Layout.tsx | 14 + src/components/LoginForm.tsx | 1 - src/components/dashboard/Actions.tsx | 97 +-- src/components/dashboard/Admin.tsx | 54 +- src/components/dashboard/Navigation.tsx | 42 +- src/components/dashboard/Stats.tsx | 54 +- src/components/dashboard/Welcome.tsx | 29 +- src/components/dashboard/index.tsx | 2 +- .../dashboard/tables/PaginatedTable.tsx | 174 +++++ src/hooks/useApi.ts | 1 - src/styles/index.css | 33 + src/utilities/apiFunctions.ts | 12 + yarn.lock | 452 +++++++++++- 19 files changed, 1604 insertions(+), 183 deletions(-) create mode 100644 src/components/Header.tsx create mode 100644 src/components/Layout.tsx create mode 100644 src/components/dashboard/tables/PaginatedTable.tsx diff --git a/package-lock.json b/package-lock.json index e2c427f..b71ac1d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,11 +18,13 @@ "@mantine/notifications": "^7.10.1", "@mantine/nprogress": "^7.10.1", "@mantine/spotlight": "^7.10.1", + "@mantine/styles": "^6.0.22", "dayjs": "^1.11.11", "js-cookie": "^3.0.5", "object-hash": "^3.0.0", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-icons": "^5.2.1", "react-router-dom": "^6.24.1", "recharts": "2" }, @@ -46,6 +48,212 @@ "vite": "^5.2.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==", + "peer": true, + "dependencies": { + "@babel/highlight": "^7.24.7", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "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==", + "peer": true, + "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-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==", + "peer": true, + "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==", + "peer": true, + "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==", + "peer": true, + "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==", + "peer": true, + "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==", + "peer": true, + "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==", + "peer": true, + "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==", + "peer": true, + "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==", + "peer": true, + "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/highlight/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==", + "peer": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/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==", + "peer": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/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==", + "peer": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/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==", + "peer": true + }, + "node_modules/@babel/highlight/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==", + "peer": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/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==", + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/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==", + "peer": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/parser": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.8.tgz", + "integrity": "sha512-WzfbgXOkGzZiXXCqk43kKwZjzwx4oulxZi3nq2TYL9mOjQv6kYwul9mz6ID36njuL7Xkp6nJEfok848Zj10j/w==", + "peer": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@babel/runtime": { "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.7.tgz", @@ -57,6 +265,178 @@ "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==", + "peer": true, + "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==", + "peer": true, + "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/traverse/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==", + "peer": true, + "engines": { + "node": ">=4" + } + }, + "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==", + "peer": true, + "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==", + "peer": true, + "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==", + "peer": true, + "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==", + "peer": true + }, + "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==", + "peer": true + }, + "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==", + "peer": true, + "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==", + "peer": true, + "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==", + "peer": 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==", + "peer": true + }, + "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==", + "peer": true, + "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==", + "peer": true + }, + "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==", + "peer": true + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", @@ -606,6 +986,54 @@ "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", "dev": true }, + "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==", + "peer": true, + "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==", + "peer": true, + "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==", + "peer": true, + "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==", + "peer": true + }, + "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==", + "peer": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, "node_modules/@mantine/charts": { "version": "7.10.1", "resolved": "https://registry.npmjs.org/@mantine/charts/-/charts-7.10.1.tgz", @@ -748,6 +1176,33 @@ "react": "^18.2.0" } }, + "node_modules/@mantine/styles": { + "version": "6.0.22", + "resolved": "https://registry.npmjs.org/@mantine/styles/-/styles-6.0.22.tgz", + "integrity": "sha512-Rud/IQp2EFYDiP4csRy2XBrho/Ct+W2/b+XbvCRTeQTmpFy/NfAKm/TWJa5zPvuv/iLTjGkVos9SHw/DteESpQ==", + "dependencies": { + "clsx": "1.1.1", + "csstype": "3.0.9" + }, + "peerDependencies": { + "@emotion/react": ">=11.9.0", + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@mantine/styles/node_modules/clsx": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz", + "integrity": "sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/@mantine/styles/node_modules/csstype": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.9.tgz", + "integrity": "sha512-rpw6JPxK6Rfg1zLOYCSwle2GFOOsnjmDYDaBwEcwoOg4qlsIVCN789VkBZDJAGi4T07gI4YSutR43t9Zz4Lzuw==" + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1293,6 +1748,12 @@ "integrity": "sha512-fOBV8C1FIu2ELinoILQ+ApxcUKz4ngq+IWUYrxSGjXzzjUALijilampwkMgEtJ+h2njAW3pi853QpzNVCHB73w==", "dev": true }, + "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==", + "peer": true + }, "node_modules/@types/prop-types": { "version": "15.7.12", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", @@ -1597,6 +2058,21 @@ "node": ">=8" } }, + "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==", + "peer": true, + "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", @@ -1628,7 +2104,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, "engines": { "node": ">=6" } @@ -1690,6 +2165,28 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, + "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==", + "peer": true + }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "peer": true, + "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", @@ -1840,7 +2337,6 @@ "version": "4.3.5", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", - "dev": true, "dependencies": { "ms": "2.1.2" }, @@ -1914,6 +2410,15 @@ "url": "https://dotenvx.com" } }, + "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==", + "peer": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, "node_modules/esbuild": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", @@ -1956,7 +2461,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, "engines": { "node": ">=10" }, @@ -2240,6 +2744,12 @@ "node": ">=8" } }, + "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==", + "peer": true + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -2296,6 +2806,15 @@ "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==", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/get-nonce": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", @@ -2421,6 +2940,18 @@ "node": ">=8" } }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "peer": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/highlight.js": { "version": "11.9.0", "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.9.0.tgz", @@ -2429,6 +2960,15 @@ "node": ">=12.0.0" } }, + "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==", + "peer": true, + "dependencies": { + "react-is": "^16.7.0" + } + }, "node_modules/ignore": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", @@ -2442,7 +2982,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -2496,6 +3035,27 @@ "loose-envify": "^1.0.0" } }, + "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==", + "peer": true + }, + "node_modules/is-core-module": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.14.0.tgz", + "integrity": "sha512-a5dFJih5ZLYlRtDc0dZWP7RiKr6xIKzmn/oAYCDvdLThadVgyJwlaoQPmRtMSpz+rk0OGAgIu+TcM9HUF0fk1A==", + "peer": true, + "dependencies": { + "hasown": "^2.0.2" + }, + "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", @@ -2566,12 +3126,30 @@ "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==", + "peer": true, + "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 }, + "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==", + "peer": true + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -2614,6 +3192,12 @@ "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==", + "peer": true + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -2691,8 +3275,7 @@ "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 + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/nanoid": { "version": "3.3.7", @@ -2794,7 +3377,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, "dependencies": { "callsites": "^3.0.0" }, @@ -2802,6 +3384,24 @@ "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==", + "peer": true, + "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", @@ -2829,11 +3429,16 @@ "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==", + "peer": true + }, "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==", - "dev": true, "engines": { "node": ">=8" } @@ -2841,8 +3446,7 @@ "node_modules/picocolors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", - "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", - "dev": true + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==" }, "node_modules/picomatch": { "version": "2.3.1", @@ -3057,6 +3661,14 @@ "react": "^18.3.1" } }, + "node_modules/react-icons": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.2.1.tgz", + "integrity": "sha512-zdbW5GstTzXaVKvGSyTaBalt7HSfuK5ovrzlpyiWHAFXndXTdd/1hdDHI4xBM1Mn7YriT6aqESucFl9kEXzrdw==", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -3251,11 +3863,27 @@ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "peer": true, + "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==", - "dev": true, "engines": { "node": ">=4" } @@ -3394,6 +4022,15 @@ "node": ">=8" } }, + "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==", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-js": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", @@ -3427,6 +4064,12 @@ "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==", + "peer": true + }, "node_modules/sugarss": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/sugarss/-/sugarss-4.0.1.tgz", @@ -3455,6 +4098,18 @@ "node": ">=8" } }, + "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==", + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/tabbable": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", @@ -3471,6 +4126,15 @@ "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==" }, + "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==", + "peer": true, + "engines": { + "node": ">=4" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -3741,6 +4405,15 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "peer": true, + "engines": { + "node": ">= 6" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 88c98fd..18f98f2 100644 --- a/package.json +++ b/package.json @@ -20,11 +20,13 @@ "@mantine/notifications": "^7.10.1", "@mantine/nprogress": "^7.10.1", "@mantine/spotlight": "^7.10.1", + "@mantine/styles": "^6.0.22", "dayjs": "^1.11.11", "js-cookie": "^3.0.5", "object-hash": "^3.0.0", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-icons": "^5.2.1", "react-router-dom": "^6.24.1", "recharts": "2" }, diff --git a/src/App.tsx b/src/App.tsx index 5da0992..e980171 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,26 +1,33 @@ import "@mantine/core/styles.css"; -import { MantineProvider } from "@mantine/core"; +import { MantineProvider, Container, ColorSchemeScript } from "@mantine/core"; import Home from "./pages/Home"; import Login from "./pages/Login"; import Demo from "./components/Home" import { BrowserRouter, Routes, Route } from "react-router-dom"; import config from "./config"; import { ConfigProvider } from "./contexts/ConfigContext"; - +import Layout from "./components/Layout"; function App() { return ( - - - - - } /> - } /> - } /> - - - - + <> + + + + + + + + } /> + } /> + } /> + + + + + + + ); } diff --git a/src/api.ts b/src/api.ts index 9795634..af2395d 100644 --- a/src/api.ts +++ b/src/api.ts @@ -11,7 +11,6 @@ export async function getCSRFToken(apiUrl: string): Promise<{ csrfToken: string, throw new Error('Failed to fetch CSRF token'); } const data = await response.json(); - console.log(data) return { csrfToken: data.csrfToken, sessionID: data.session_id }; } catch (error) { console.error('Error fetching CSRF token:', error); diff --git a/src/components/Header.tsx b/src/components/Header.tsx new file mode 100644 index 0000000..fc19865 --- /dev/null +++ b/src/components/Header.tsx @@ -0,0 +1,55 @@ +import { useState } from 'react'; +import { Container, Group, Burger, Drawer, Stack } from '@mantine/core'; +import { useDisclosure } from '@mantine/hooks'; +import { Link } from 'react-router-dom'; // Import Link from react-router-dom + +import '../styles/index.css'; + +const links = [ + { link: '/', label: 'Home' }, + { link: '/demo', label: 'Demo' }, +]; + +export function HeaderSimple() { + const [opened, { toggle, close }] = useDisclosure(false); + const [active, setActive] = useState(links[0].link); + + const items = links.map((link) => ( + { + setActive(link.link); + close(); + }} + > + {link.label} + + )); + + return ( +
+ + + {items} + + + + + + {items} + + +
+ ); +} + +export default HeaderSimple; diff --git a/src/components/Home.tsx b/src/components/Home.tsx index 2e589bc..0b590f8 100644 --- a/src/components/Home.tsx +++ b/src/components/Home.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect, useCallback } from 'react'; +import { useState, useEffect } from 'react'; import { Button, Text, Group, Container, Paper, Title } from '@mantine/core'; import { useNavigate } from 'react-router-dom'; import { useConfig } from '../contexts/ConfigContext'; @@ -6,7 +6,6 @@ import { apiCall } from '../api'; import { useCsrfToken } from '../hooks/useCsrfToken'; import { getApiEndpointFunctions } from '../utilities/apiFunctions'; import { usePaginatedApi } from '../hooks/useApi'; -import { getCookie, setCookie } from '../utilities/getCookie'; const Home = (): JSX.Element => { const [page, setPage] = useState(1); @@ -17,7 +16,7 @@ const Home = (): JSX.Element => { const [userName, setUserName] = useState(null); const api = getApiEndpointFunctions(); - const { data: paginatedData, loading, reload } = usePaginatedApi(api.logs.get, page, 5); + const { data: paginatedData } = usePaginatedApi(api.logs.get, page, 5); useEffect(() => { if (paginatedData && paginatedData.results) { @@ -29,22 +28,21 @@ const Home = (): JSX.Element => { setPage(page + 1); }; - function updateCSRFToken(newToken: string) { - const csrfCookieName = 'csrftoken'; - const currentToken = getCookie(csrfCookieName); + // function updateCSRFToken(newToken: string) { + // const csrfCookieName = 'csrftoken'; + // const currentToken = getCookie(csrfCookieName); - if (currentToken !== newToken) { - setCookie(csrfCookieName, newToken); - console.log("set new token") - } - } + // if (currentToken !== newToken) { + // setCookie(csrfCookieName, newToken); + // } + // } - const logout = useCallback(async () => { - await fetchCsrfToken(); - updateCSRFToken(csrftoken); - await api.auth.logout(); - await reload(); - }, [reload]); + // const logout = useCallback(async () => { + // await fetchCsrfToken(); + // updateCSRFToken(csrftoken); + // await api.auth.logout(); + // await reload(); + // }, [reload]); @@ -52,15 +50,13 @@ const Home = (): JSX.Element => { try { await fetchCsrfToken(); const url = `${config.apiUrl}/logout/`; - const data = await apiCall(url, 'POST', null, csrftoken); - console.log(data); + await apiCall(url, 'POST', null, csrftoken); navigate('/login'); } catch (error) { console.error('There was a problem with the fetch operation:', error); } }; - const handleWhoAmI = async () => { try { const url = `${config.apiUrl}/whoami/`; diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx new file mode 100644 index 0000000..9db3760 --- /dev/null +++ b/src/components/Layout.tsx @@ -0,0 +1,14 @@ +import React from 'react'; +import Header from './Header'; +import { LayoutRouteProps } from 'react-router-dom'; + +const Layout: React.FC = ({ children }) => { + return ( +
+
+
{children}
+
+ ); +}; + +export default Layout; diff --git a/src/components/LoginForm.tsx b/src/components/LoginForm.tsx index 0456a69..37548af 100644 --- a/src/components/LoginForm.tsx +++ b/src/components/LoginForm.tsx @@ -19,7 +19,6 @@ const LoginForm = () => { await fetchCsrfToken(); const url = `${config.apiUrl}/login/`; const data = await apiCall(url, 'POST', { username, password }, csrftoken); - console.log('Login successful:', data); navigate('/'); } catch (error: any) { if (error.message === '401') { diff --git a/src/components/dashboard/Actions.tsx b/src/components/dashboard/Actions.tsx index cff31e2..f693595 100644 --- a/src/components/dashboard/Actions.tsx +++ b/src/components/dashboard/Actions.tsx @@ -1,81 +1,34 @@ -import { useState, useEffect } from 'react'; -import { Button, Text, Group, Container, Paper, Title, Table } from '@mantine/core'; -import { useNavigate } from 'react-router-dom'; -import { useConfig } from '../../contexts/ConfigContext'; -import { apiCall } from '../../api'; -import { useCsrfToken } from '../../hooks/useCsrfToken'; +import { useState } from 'react'; import { getApiEndpointFunctions } from '../../utilities/apiFunctions'; -import { usePaginatedApi } from '../../hooks/useApi'; - +import PaginatedTable from './tables/PaginatedTable'; const Actions = (): JSX.Element => { - const [page, setPage] = useState(1); - const [data, setData] = useState([]); - const navigate = useNavigate(); - const { csrftoken, fetchCsrfToken } = useCsrfToken(); - const { config } = useConfig(); - const [userName, setUserName] = useState(null); + const [pageSize] = useState(5); const api = getApiEndpointFunctions(); - const { data: paginatedData, loading, reload } = usePaginatedApi(api.logs.get, page, 5); - - useEffect(() => { - if (paginatedData && paginatedData.results) { - setData(paginatedData.results); - } - }, [paginatedData]); - - const nextPage = async () => { - setPage(page + 1); - }; - - const handleWhoAmI = async () => { - try { - const url = `${config.apiUrl}/whoami/`; - const data = await apiCall(url, 'GET', null, csrftoken); - if (data.user === null) { - navigate('/login'); - return; - } - setUserName(data.user.username); - } catch (error) { - console.error('There was a problem with the fetch operation:', error); - } - }; - - useEffect(() => { - handleWhoAmI(); - }, []); - return ( - - - Recent Actions - - - {data && data.map((item: any) => { - const date = new Date(item.action_time); - const options: Intl.DateTimeFormatOptions = { year: 'numeric', month: 'long', day: 'numeric' }; - const formattedDate = date.toLocaleDateString('en-US', options); - - return ( - - - {item.content_type} {item.object_repr} - - - {formattedDate} - - - ); - })} - -
-
-
+ <> + + console.log(`Editing group ${id}`), + Delete: (id: number) => console.log(`Deleting group ${id}`) + }} + /> + ); - - } -export default Actions; \ No newline at end of file +export default Actions; diff --git a/src/components/dashboard/Admin.tsx b/src/components/dashboard/Admin.tsx index 80d5cd6..79b40f8 100644 --- a/src/components/dashboard/Admin.tsx +++ b/src/components/dashboard/Admin.tsx @@ -1,15 +1,51 @@ -import { Text, Container, Paper, Title } from '@mantine/core'; - +import { Text, Paper, Title, Table } from '@mantine/core'; const Admin = () => { + const items = { + "Administration": "admin", + "Authentication and Authorization": "authentication", + "Auth": "auth", + "Token": "token", + "Core": "core", + "Dns": "dns", + "Guardian": "guardian", + "Hosts": "hosts", + "Log": "log", + "Network": "network", + "Taggit": "taggit", + "User": "user", + }; + return ( - - - Admin - This is broken on my prod, all the links lead to the home page ;/ - - + + Admin + These links are broken on my prod, all the links lead to the home page :/ + + + {Object.entries(items).map(([label, link]) => ( + + + + (e.target as HTMLAnchorElement).style.textDecoration = "underline"} + onMouseLeave={(e) => (e.target as HTMLAnchorElement).style.textDecoration = "none"} + > + {label} + + + + + ))} + +
+
); }; -export default Admin; \ No newline at end of file +export default Admin; diff --git a/src/components/dashboard/Navigation.tsx b/src/components/dashboard/Navigation.tsx index f710359..abafb1d 100644 --- a/src/components/dashboard/Navigation.tsx +++ b/src/components/dashboard/Navigation.tsx @@ -1,14 +1,42 @@ -import { Text, Container, Paper, Title } from '@mantine/core'; +import { Text, Paper, Title, Table } from '@mantine/core'; const Navigation = () => { + const items = { + "List Hosts": "/hosts", + "Add Host": "/hosts/add", + "DNS Records": "/dns", + "Feature or Bug?": "/request", + "Profile": "/profile", + } return ( - - - Navigation - Text - - + + Navigation + + + {Object.entries(items).map(([label, link]) => ( + + + + (e.target as HTMLAnchorElement).style.textDecoration = "underline"} + onMouseLeave={(e) => (e.target as HTMLAnchorElement).style.textDecoration = "none"} + > + {label} + + + + + ))} + +
+
); }; diff --git a/src/components/dashboard/Stats.tsx b/src/components/dashboard/Stats.tsx index 9d00e53..f97648a 100644 --- a/src/components/dashboard/Stats.tsx +++ b/src/components/dashboard/Stats.tsx @@ -1,6 +1,6 @@ import { useEffect, useState } from 'react'; import { useApiData } from '../../hooks/useApi'; -import { Container, Paper, Title } from '@mantine/core'; +import { Paper, Title } from '@mantine/core'; const Stats = () => { const [data, setData] = useState([]); @@ -21,34 +21,32 @@ const Stats = () => { }, [apiData]); return ( - - - Stats + + Stats - {loading &&

Loading...

} - {error &&

Error: {error.message}

} - {data && ( - <> - - Hosts - {hostItems.map((item) => ( -

- {itemText(item)}: {data[item]} -

- ))} -
- - Users - {userItems.map((item) => ( -

- {data[item]} {itemText(item)} -

- ))} -
- - )} -
-
+ {loading &&

Loading...

} + {error &&

Error: {error.message}

} + {data && ( + <> + + Hosts + {hostItems.map((item) => ( +

+ {itemText(item)}: {data[item]} +

+ ))} +
+ + Users + {userItems.map((item) => ( +

+ {data[item]} {itemText(item)} +

+ ))} +
+ + )} + ); }; diff --git a/src/components/dashboard/Welcome.tsx b/src/components/dashboard/Welcome.tsx index 6bada82..df95f24 100644 --- a/src/components/dashboard/Welcome.tsx +++ b/src/components/dashboard/Welcome.tsx @@ -1,4 +1,4 @@ -import { Text, Container, Paper, Title } from '@mantine/core'; +import { Text, Paper, Title } from '@mantine/core'; import { useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { useConfig } from '../../contexts/ConfigContext'; @@ -34,20 +34,19 @@ const Welcome = () => { }, []); return ( - - - Welcome to openIPAM, {userName} - Intro para -

- We are now using Issues on GitHub to help aid us with features and bugs. Please make an issue on GitHub to give us feedback. - Item to consider when using the new interface: - Permissions - Do you have all your permissions? - Hosts - Do you see all your hosts? - DNS Entries - Do you see all DNS Entries? - If you have any questions, please email: openipam@lists.usu.edu -

-
-
+ + + Welcome to openIPAM, {userName} + Intro para +

+ We are now using Issues on GitHub to help aid us with features and bugs. Please make an issue on GitHub to give us feedback. + Item to consider when using the new interface: + Permissions - Do you have all your permissions? + Hosts - Do you see all your hosts? + DNS Entries - Do you see all DNS Entries? + If you have any questions, please email: openipam@lists.usu.edu +

+
); }; diff --git a/src/components/dashboard/index.tsx b/src/components/dashboard/index.tsx index f13c6e1..a997e8f 100644 --- a/src/components/dashboard/index.tsx +++ b/src/components/dashboard/index.tsx @@ -10,7 +10,7 @@ import { Grid } from '@mantine/core'; const DashBoard = () => { return ( <> - + diff --git a/src/components/dashboard/tables/PaginatedTable.tsx b/src/components/dashboard/tables/PaginatedTable.tsx new file mode 100644 index 0000000..5b94dce --- /dev/null +++ b/src/components/dashboard/tables/PaginatedTable.tsx @@ -0,0 +1,174 @@ +import { PaginatedData } from '../../../types/api'; +import { usePaginatedApi } from '../../../hooks/useApi'; +import { useState, useEffect } from 'react'; +import { QueryRequest } from '../../../utilities/apiFunctions'; +import { Button, Paper, Title, Table, Text, Select, Group, Checkbox, Notification, Dialog } from '@mantine/core'; +import { FaArrowLeft, FaArrowRight, FaRegCircleXmark, FaRegCircleCheck } from 'react-icons/fa6'; + +interface BasePaginatedTableProps { + getFunction: QueryRequest>; + defPageSize: number; + title: string; + neededAttr: string[]; + morePageSizes?: string[]; +} + +interface EditablePaginatedTableProps extends BasePaginatedTableProps { + editableObj: true; + actions: string[]; + actionFunctions: Record void>; +} + +interface NonEditablePaginatedTableProps extends BasePaginatedTableProps { + editableObj?: false; +} + +type PaginatedTableProps = EditablePaginatedTableProps | NonEditablePaginatedTableProps; + + +const PaginatedTable = ({ getFunction, defPageSize, title, neededAttr, morePageSizes, editableObj }: PaginatedTableProps): JSX.Element => { + const [data, setData] = useState([]); + const [maxPages, setMaxPages] = useState(0); + const [pageSize, setPageSize] = useState(defPageSize); + const [page, setPage] = useState(1); + const [selectedObjs, setSelectedObjs] = useState>(new Set()); + const [notification, setNotification] = useState(null); + + const pageSizes = ['5', '10', '20']; + if (morePageSizes) { + const uniqueSizes = new Set([...pageSizes, ...morePageSizes]); + pageSizes.length = 0; + pageSizes.push(...Array.from(uniqueSizes).sort((a, b) => parseInt(a) - parseInt(b))); + } + + const { data: paginatedData } = usePaginatedApi(getFunction, page, pageSize); + + const nextPage = () => setPage(page + 1); + const prevPage = () => setPage(page > 1 ? page - 1 : 1); + + const handlePageSizeChange = (value: string | null) => setPageSize(parseInt(value || '5')); + + const handleFormatHeader = (header: string) => header.replace(/[_-]/g, ' ').replace(/\b\w/g, (char: string) => char.toUpperCase()); + + const submitChange = () => { + setNotification(['Changes submitted successfully', 'Success']); + // setNotification(['Error while submitting', 'Failure']); + } + + useEffect(() => { + if (paginatedData && paginatedData.results) { + setData(paginatedData.results); + setMaxPages(Math.ceil(paginatedData.count / pageSize)); + } + }, [paginatedData, pageSize]); + + const handleCheckboxChange = (item: any, checked: boolean) => { + setSelectedObjs(prevSelectedObjs => { + const updatedSelectedObjs = new Set(prevSelectedObjs); + if (checked) { + updatedSelectedObjs.add(item.id); + } else { + updatedSelectedObjs.delete(item.id); + } + return updatedSelectedObjs; + }); + }; + + return ( + <> + + {title} + + + + {editableObj && } + {neededAttr.map(attr => ( + {handleFormatHeader(attr)} + ))} + {neededAttr.some(attr => /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?[-+]\d{2}:\d{2}$/.test(attr)) && ( + Date + )} + + + + {data && data.map((item: any) => { + const dateAttrs: string[] = []; + + return ( + + {editableObj && ( + + handleCheckboxChange(item, event.currentTarget.checked)} + /> + + )} + {neededAttr.map(attr => { + const value = item[attr]; + const isoDateRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?[-+]\d{2}:\d{2}$/; + + if (isoDateRegex.test(value)) { + const date = new Date(value); + const options: Intl.DateTimeFormatOptions = { year: 'numeric', month: 'long', day: 'numeric' }; + dateAttrs.push(date.toLocaleDateString('en-US', options)); + return {date.toLocaleDateString('en-US', options)}; + } else if (Array.isArray(value)) { + return {value.join(', ')}; + } else { + return {value || 'N/A'}; + } + })} + {dateAttrs.length > 0 && ( + + {dateAttrs.join(' ')} + + )} + + ); + })} + +
+ +
+ + + {editableObj && ( - - + - {editableObj && ( - {editableObj && ( -
- - -
+ { {editableObj && (
- + + ); +}; + +export default Dns; \ No newline at end of file diff --git a/src/routes.tsx b/src/routes.tsx index 15cbdda..5422ad0 100644 --- a/src/routes.tsx +++ b/src/routes.tsx @@ -3,10 +3,12 @@ import { RouteObject } from 'react-router-dom'; const Home = React.lazy(() => import('./pages/Home')); const Login = React.lazy(() => import('./pages/Login')); +const Dns = React.lazy(() => import('./pages/Dns')); const routes: RouteObject[] = [ { path: '/', element: }, { path: '/login', element: }, + { path: '/dns', element: }, ]; export default routes; diff --git a/src/utilities/apiFunctions.ts b/src/utilities/apiFunctions.ts index f851675..9441832 100644 --- a/src/utilities/apiFunctions.ts +++ b/src/utilities/apiFunctions.ts @@ -33,11 +33,8 @@ export const getApiEndpointFunctions = < /** * Logout the current user */ - logout: requestGenerator< - HttpMethod.POST, - void, - API.GenericResponse | StrictTypeChecking - >(HttpMethod.POST, 'logout/', { headers: { 'Content-Type': 'application/json', 'X-CSRFToken': getCookie('csrftoken') ?? '' } }), + logout: requestGenerator + (HttpMethod.POST, 'logout/', { headers: { 'Content-Type': 'application/json', 'X-CSRFToken': getCookie('csrftoken') ?? '' } }), /** * Gets the current user for testing purposes only :D */ @@ -103,7 +100,28 @@ export const getApiEndpointFunctions = < API.PaginationParams, API.PaginatedData | StrictTypeChecking >(HttpMethod.GET, 'groups/', { headers: { 'Content-Type': 'application/json' } }), - } + }, + /** + * DNS API + */ + dns: { + /** + * Gets DNSRecord objects + * Sortable: true + */ + get: requestGenerator< + HttpMethod.GET, + API.PaginationParams, + API.PaginatedData | StrictTypeChecking + >(HttpMethod.GET, 'dns/', { headers: { 'Content-Type': 'application/json' } }), + byId: (id: string | number) => ({ + delete: requestGenerator( + HttpMethod.DELETE, + `dns/${id}/`, + { headers: { 'Content-Type': 'application/json', 'X-CSRFToken': getCookie('csrftoken') ?? '' } } + ), + }) + }, }); declare global { From acc3d7cdc3a651f073449b841474ff266f8ab00b Mon Sep 17 00:00:00 2001 From: Treyson Date: Fri, 26 Jul 2024 11:33:46 -0600 Subject: [PATCH 22/96] add searchable fields + general formatting --- src/components/LoginForm.tsx | 10 ++- src/components/Navbar.tsx | 10 ++- src/components/dashboard/Actions.tsx | 5 ++ src/components/tables/PaginatedTable.tsx | 93 ++++++++++++------------ src/contexts/ConfigContext.tsx | 6 +- src/hooks/useCsrfToken.ts | 7 +- src/pages/Dns.tsx | 10 ++- 7 files changed, 85 insertions(+), 56 deletions(-) diff --git a/src/components/LoginForm.tsx b/src/components/LoginForm.tsx index 0eccc75..62dd629 100644 --- a/src/components/LoginForm.tsx +++ b/src/components/LoginForm.tsx @@ -1,4 +1,12 @@ -import { TextInput, Button, Text, Group, Container, Paper, Title } from '@mantine/core'; +import { + TextInput, + Button, + Text, + Group, + Container, + Paper, + Title +} from '@mantine/core'; import { useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { useConfig } from '../contexts/ConfigContext'; diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index 076d576..b1a694f 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -1,5 +1,13 @@ import { useState } from 'react'; -import { Container, Group, Burger, Drawer, Stack, Menu, Title } from '@mantine/core'; +import { + Container, + Group, + Burger, + Drawer, + Stack, + Menu, + Title +} from '@mantine/core'; import { useDisclosure } from '@mantine/hooks'; import { Link } from 'react-router-dom'; diff --git a/src/components/dashboard/Actions.tsx b/src/components/dashboard/Actions.tsx index 684fe34..31c4570 100644 --- a/src/components/dashboard/Actions.tsx +++ b/src/components/dashboard/Actions.tsx @@ -2,13 +2,16 @@ import { useState } from 'react'; import { getApiEndpointFunctions } from '../../utilities/apiFunctions'; import PaginatedTable from '../tables/PaginatedTable'; import { Button } from '@mantine/core'; +import { useNavigate } from 'react-router-dom'; const Actions = (): JSX.Element => { const [pageSize] = useState(5); + const navigate = useNavigate(); const api = getApiEndpointFunctions(); const handleLogout = async () => { try { api.auth.logout(); + navigate('/login'); } catch (error) { console.error('There was a problem with the fetch operation:', error); } @@ -36,6 +39,8 @@ const Actions = (): JSX.Element => { ChangeSourceInternal: (id: number) => console.log(`Editing group ${id}`), ChangeSourceLDAP: (id: number) => console.log(`Deleting group ${id}`) }} + searchable={true} + searchableFields={['name']} /> diff --git a/src/components/tables/PaginatedTable.tsx b/src/components/tables/PaginatedTable.tsx index 261fefe..f9aa855 100644 --- a/src/components/tables/PaginatedTable.tsx +++ b/src/components/tables/PaginatedTable.tsx @@ -2,77 +2,58 @@ import { PaginatedData } from '../../types/api'; import { usePaginatedApi } from '../../hooks/useApi'; import { useState, useEffect } from 'react'; import { QueryRequest } from '../../utilities/apiFunctions'; -import { Button, Paper, Title, Table, Text, Select, Group, Checkbox, Notification, Dialog, Pagination } from '@mantine/core'; -import { FaSort, FaSortUp, FaSortDown } from 'react-icons/fa'; -import { FaRegCircleXmark, FaRegCircleCheck } from 'react-icons/fa6'; +import { + Button, + Paper, + Title, + Table, + Text, + Select, + Group, + Checkbox, + Notification, + Dialog, + Pagination, + TextInput +} from '@mantine/core'; +import { + FaRegCircleXmark, + FaRegCircleCheck, + FaSort, + FaSortUp, + FaSortDown +} from 'react-icons/fa6'; interface BasePaginatedTableProps { - /** - * Function to get data from the API - */ getFunction: QueryRequest>; - /** - * Default page size - */ defPageSize: number; - /** - * Title of the table - */ title: string; - /** - * List of attributes to display - */ neededAttr: string[]; - /** - * Additional page sizes to display in the page size dropdown. Displays in - * order provided. - */ morePageSizes?: string[]; - /** - * Whether the table is sortable - */ - sortable?: boolean; - /** - * Whether to override the default page sizes with the provided page sizes - */ overridePageSizes?: boolean; + sortable?: boolean; + sortableFields?: string[]; + searchable?: boolean; + searchableFields?: string[]; } interface EditablePaginatedTableProps extends BasePaginatedTableProps { - /** - * Whether the table has editable objects - */ editableObj: true; - /** - * List of actions to display in the action dropdown - */ actions: string[]; - /** - * Object containing functions to call when submitting an action - */ actionFunctions: Record void>; } interface NonEditablePaginatedTableProps extends BasePaginatedTableProps { - /** - * Default to false - */ editableObj?: false; } type PaginatedTableProps = EditablePaginatedTableProps | NonEditablePaginatedTableProps; - -/** - * PaginatedTable component that displays data in a paginated table - * @param props - * @returns JSX.Element - */ const PaginatedTable = (props: PaginatedTableProps): JSX.Element => { const actions = (props as EditablePaginatedTableProps).actions; const actionFunctions = (props as EditablePaginatedTableProps).actionFunctions; - const { getFunction, defPageSize, title, neededAttr, morePageSizes, overridePageSizes, editableObj, sortable } = props; + const { getFunction, defPageSize, title, neededAttr, morePageSizes, overridePageSizes, editableObj, sortable, sortableFields, searchable, searchableFields } = props; const [data, setData] = useState([]); const [maxPages, setMaxPages] = useState(0); const [pageSize, setPageSize] = useState(defPageSize); @@ -82,6 +63,7 @@ const PaginatedTable = (props: PaginatedTableProps): JSX.Element => { const [action, setAction] = useState(actions[0]); const [orderBy, setOrderBy] = useState(''); const [direction, setDirection] = useState('asc'); + const [searchTerms, setSearchTerms] = useState>({}); const pageSizes = ['5', '10', '20']; if (overridePageSizes) { pageSizes.length = 0; } @@ -97,11 +79,18 @@ const PaginatedTable = (props: PaginatedTableProps): JSX.Element => { setPageSize(parseInt(value || '5')); } + const handleSearchChange = (field: string, value: string) => { + setSearchTerms(prevTerms => ({ ...prevTerms, [field]: value })); + } + const { data: paginatedData } = usePaginatedApi( getFunction, page, pageSize, - orderBy ? { "order_by": orderBy, "direction": direction } : {} + { + ...orderBy ? { "order_by": orderBy, "direction": direction } : {}, + ...Object.fromEntries(Object.entries(searchTerms).filter(([_, v]) => v)) + } ); const handleActionChange = (value: string | null) => { @@ -182,11 +171,19 @@ const PaginatedTable = (props: PaginatedTableProps): JSX.Element => { {handleFormatHeader(attr)} - {sortable && ( + {sortable && sortableFields?.includes(attr) && ( )} + {searchable && searchableFields?.includes(attr) && ( + handleSearchChange(attr, e.currentTarget.value)} + size="xs" + /> + )} ))} @@ -250,7 +247,7 @@ const PaginatedTable = (props: PaginatedTableProps): JSX.Element => { {editableObj && (
); }; diff --git a/src/utilities/apiFunctions.ts b/src/utilities/apiFunctions.ts index 3bda5a2..50159d4 100644 --- a/src/utilities/apiFunctions.ts +++ b/src/utilities/apiFunctions.ts @@ -41,8 +41,9 @@ export const getApiEndpointFunctions = < /** * Logout the current user */ - logout: requestGenerator - (HttpMethod.POST, 'logout/', { headers: { 'Content-Type': 'application/json', 'X-CSRFToken': getCookie('csrftoken') ?? '' } }), + logout: requestGenerator< + HttpMethod.POST + >(HttpMethod.POST, 'logout/', { headers: { 'Content-Type': 'application/json', 'X-CSRFToken': getCookie('csrftoken') ?? '' } }), /** * Get the CSRF token for initial login From 433c361927bae8292c29d5a9b29b8ebfbf7e6f26 Mon Sep 17 00:00:00 2001 From: Treyson Date: Fri, 26 Jul 2024 13:43:26 -0600 Subject: [PATCH 26/96] actions no longer required oops --- src/components/dashboard/Actions.tsx | 7 ++----- src/components/tables/PaginatedTable.tsx | 12 ++++++------ 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/components/dashboard/Actions.tsx b/src/components/dashboard/Actions.tsx index 31c4570..9870f88 100644 --- a/src/components/dashboard/Actions.tsx +++ b/src/components/dashboard/Actions.tsx @@ -1,11 +1,9 @@ -import { useState } from 'react'; import { getApiEndpointFunctions } from '../../utilities/apiFunctions'; import PaginatedTable from '../tables/PaginatedTable'; import { Button } from '@mantine/core'; import { useNavigate } from 'react-router-dom'; const Actions = (): JSX.Element => { - const [pageSize] = useState(5); const navigate = useNavigate(); const api = getApiEndpointFunctions(); const handleLogout = async () => { @@ -19,16 +17,15 @@ const Actions = (): JSX.Element => { return ( <> void>; + actions?: string[]; + actionFunctions?: Record void>; } interface NonEditablePaginatedTableProps extends BasePaginatedTableProps { @@ -50,8 +50,8 @@ interface NonEditablePaginatedTableProps extends BasePaginatedTableProps { type PaginatedTableProps = EditablePaginatedTableProps | NonEditablePaginatedTableProps; const PaginatedTable = (props: PaginatedTableProps): JSX.Element => { - const actions = (props as EditablePaginatedTableProps).actions; - const actionFunctions = (props as EditablePaginatedTableProps).actionFunctions; + const actions = (props as EditablePaginatedTableProps).actions ?? []; + const actionFunctions = (props as EditablePaginatedTableProps).actionFunctions ?? {}; const { getFunction, defPageSize, title, neededAttr, morePageSizes, overridePageSizes, editableObj, sortable, sortableFields, searchable, searchableFields } = props; const [data, setData] = useState([]); @@ -60,7 +60,7 @@ const PaginatedTable = (props: PaginatedTableProps): JSX.Element => { const [page, setPage] = useState(1); const [selectedObjs, setSelectedObjs] = useState>(new Set()); const [notification, setNotification] = useState(null); - const [action, setAction] = useState(actions[0]); + const [action, setAction] = useState(actions[0] ?? ''); const [orderBy, setOrderBy] = useState(''); const [direction, setDirection] = useState('asc'); const [searchTerms, setSearchTerms] = useState>({}); @@ -228,7 +228,7 @@ const PaginatedTable = (props: PaginatedTableProps): JSX.Element => { value={pageSize.toString()} onChange={handlePageSizeChange} /> - {editableObj && ( + {editableObj && actions.length > 0 && ( + + + )} + {maxPages !== 1 && ( + + Select number of rows - )} -
- - : } - color='blue' - onClose={() => setNotification(null)} - /> + + )} + {notification && ( + setNotification(null)}> + + {notification[0]} + - {editableObj && ( -
- - -
- )} - + )} ); }; diff --git a/src/pages/DomainDetail.tsx b/src/pages/DomainDetail.tsx index f67c4a1..47db754 100644 --- a/src/pages/DomainDetail.tsx +++ b/src/pages/DomainDetail.tsx @@ -6,17 +6,26 @@ const DomainDetail = () => { const { domain } = useParams<{ domain: string }>() as { domain: string | number }; const api = getApiEndpointFunctions(); const title = 'DNS Records for ' + domain; + + const handleEditFunction = (dnsName: string) => { + return (editValues: any) => { + return api.dns.byId(`${dnsName}`).update(editValues); + }; + }; + return ( ); } diff --git a/src/types/api.ts b/src/types/api.ts index 79f0704..df3b369 100644 --- a/src/types/api.ts +++ b/src/types/api.ts @@ -231,6 +231,37 @@ export interface User { date_joined: string; } +export interface DNSRecord { + /** + * The display name of the DNS record. + */ + name: string; + /** + * the content of the DNS record. (IP address, hostname, etc.) + */ + content: string; + /** + * The dns type of the DNS record. + */ + dns_type: string; + /** + * The time-to-live of the DNS record. + */ + ttl: number; + /** + * The host that the DNS record is associated with. + */ + host: string; + /** + * The ID of the DNS record. + */ + id: number; + /** + * The orphaned-dns url (only works if the record is orphaned) + */ + url: string; +} + /** * Interface for the Filter model. */ diff --git a/src/utilities/apiFunctions.ts b/src/utilities/apiFunctions.ts index 81e1aa6..2e139ed 100644 --- a/src/utilities/apiFunctions.ts +++ b/src/utilities/apiFunctions.ts @@ -156,6 +156,14 @@ export const getApiEndpointFunctions = < `dns/${id}/`, { headers: { 'Content-Type': 'application/json', 'X-CSRFToken': getCookie('csrftoken') ?? '' } } ), + /** + * Updates a DNSRecord object given an objects ID and new data + */ + update: requestGenerator< + HttpMethod.PUT, + Partial, + API.DNSRecord | StrictTypeChecking + >(HttpMethod.PUT, `dns/${id}/`, { headers: { 'Content-Type': 'application/json', 'X-CSRFToken': getCookie('csrftoken') ?? '' } }), }) }, /** @@ -171,6 +179,18 @@ export const getApiEndpointFunctions = < API.PaginationParams, API.PaginatedData | StrictTypeChecking >(HttpMethod.GET, 'domains/', { headers: { 'Content-Type': 'application/json' } }), + + byId: (id: string | number) => ({ + getRecords: requestGenerator< + HttpMethod.GET, + API.PaginationParams, + API.PaginatedData | StrictTypeChecking + >( + HttpMethod.GET, + `domains/${id}/records/`, + { headers: { 'Content-Type': 'application/json' } } + ), + }) }, /** * Hosts API From e2169ac0263776b2b79788c526f148e4887385fe Mon Sep 17 00:00:00 2001 From: Treyson Date: Mon, 12 Aug 2024 12:26:23 -0600 Subject: [PATCH 46/96] handle instances where data is empty. --- src/components/tables/PaginatedTable.tsx | 178 ++++++++++++++--------- 1 file changed, 107 insertions(+), 71 deletions(-) diff --git a/src/components/tables/PaginatedTable.tsx b/src/components/tables/PaginatedTable.tsx index 25f8871..f8a6352 100644 --- a/src/components/tables/PaginatedTable.tsx +++ b/src/components/tables/PaginatedTable.tsx @@ -14,12 +14,17 @@ import { Notification, Dialog, Pagination, - TextInput + TextInput, + ActionIcon, + Tooltip } from '@mantine/core'; import { FaSort, FaSortUp, - FaSortDown + FaSortDown, + FaCheck, + FaXmark, + FaPencil } from 'react-icons/fa6'; interface BasePaginatedTableProps { @@ -262,80 +267,111 @@ const PaginatedTable = (props: PaginatedTableProps): JSX.Element => { } -
- - - {editableObj && } - {neededAttr.map(attr => ( - - ))} - - - - {editableObj && } + {data.length === 0 ? ( + No data available + ) : ( +
+
+ + {editableObj && } {neededAttr.map(attr => ( - - - {handleFormatHeader(attr)} - {sortable && sortableFields?.includes(attr) && ( - - )} - {searchable && searchableFields?.includes(attr) && ( - handleSearchChange(attr, e.currentTarget.value)} - size='xs' - /> - )} - - + ))} - {editableObj && Edit} - - - - {data.map((item: any) => { - const isEditing = editingRow === item.id; - return ( - - {editableObj && ( - - obj.id === item.id)} - onChange={(event) => handleCheckboxChange(item, event.currentTarget.checked)} - /> - - )} - {neededAttr.map(attr => ( - - {isEditing && editableFields?.includes(attr) ? ( + + + + {editableObj && } + {neededAttr.map(attr => ( + + + {handleFormatHeader(attr)} + {sortable && sortableFields?.includes(attr) && ( + + )} + {searchable && searchableFields?.includes(attr) && ( handleEditInputChange(attr, e.currentTarget.value)} + placeholder={`Search ${handleFormatHeader(attr)}`} + value={searchTerms[attr] || ''} + onChange={(e) => handleSearchChange(attr, e.currentTarget.value)} + size='xs' /> - ) : ( - item[attr] )} - - ))} - {editableObj && ( - - {isEditing ? ( - - ) : ( - - )} - - )} - - ); - })} - -
-
+ + + ))} + {editableObj && Edit} + + + + {data.map((item: any) => { + const isEditing = editingRow === item.id; + return ( + + {editableObj && ( + + obj.id === item.id)} + onChange={(event) => handleCheckboxChange(item, event.currentTarget.checked)} + /> + + )} + {neededAttr.map(attr => ( + + {isEditing && editableFields?.includes(attr) ? ( + handleEditInputChange(attr, e.currentTarget.value)} + /> + ) : ( + item[attr] + )} + + ))} + {editableObj && ( + +
+ {isEditing ? ( + <> + + handleEditSubmit(item)} + > + + + + + { + setEditingRow(null); + setEditValues({}); + }} + > + + + + + ) : ( + + handleEditClick(item)}> + + + + )} +
+ +
+ + )} +
+ ); + })} +
+ +
+ )} {actions.length > 0 && ( - - - )} - {maxPages !== 1 && ( - - Select number of rows - + + + )} + {maxPages !== 1 && ( + + Select number of rows + - - - )} - {maxPages !== 1 && ( - - Select number of rows +
+ {data.length >= +pageSizes[0] && ( + )} +
{notification && ( - setNotification(null)}> - - {notification[0]} - + + : } + color='blue' + onClose={() => setNotification(null)} + /> )} + {editableObj && ( +
+ + +
+ )}
); diff --git a/src/pages/DomainDetail.tsx b/src/pages/DomainDetail.tsx index 47db754..2035c16 100644 --- a/src/pages/DomainDetail.tsx +++ b/src/pages/DomainDetail.tsx @@ -19,7 +19,6 @@ const DomainDetail = () => { getFunction={api.domain.byId(domain).getRecords} title={title} neededAttr={['name', 'content', 'ttl', 'dns_type']} - morePageSizes={['1']} sortable={true} searchable={true} searchableFields={['name', 'content', 'dns']} From 34d8c4df8f79f89649122082627606512263cef0 Mon Sep 17 00:00:00 2001 From: Treyson Date: Tue, 13 Aug 2024 14:32:47 -0600 Subject: [PATCH 49/96] clean unused code, removed most unecessary style tags, use mantine silly --- src/components/DomainGrid.tsx | 23 ++++++---------- src/components/Navbar.tsx | 34 +++++++++++++++++------- src/components/tables/PaginatedTable.tsx | 9 ++++--- 3 files changed, 38 insertions(+), 28 deletions(-) diff --git a/src/components/DomainGrid.tsx b/src/components/DomainGrid.tsx index 4457753..5d47800 100644 --- a/src/components/DomainGrid.tsx +++ b/src/components/DomainGrid.tsx @@ -25,16 +25,15 @@ const DomainGrid = (): JSX.Element => { } }, [data]); - const calculateSpan = () => {//Ignore this for now - const isMobile = useMediaQuery(`(max-width: ${em(900)})`); + const calculateSpan = () => { + const isMobile = useMediaQuery(`(max-width: ${em(1100)})`); if (isMobile) { return 12; } - return 3; }; - // const count = data?.count || 0; + const span = calculateSpan(); return ( @@ -58,7 +57,7 @@ const DomainGrid = (): JSX.Element => { {data?.results?.map((domain: any, index: number) => ( - + {domain.name} @@ -67,11 +66,13 @@ const DomainGrid = (): JSX.Element => { {domain.record_count} - + {domain.description} { - {/* - - */} diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index 1cc1e79..4239c93 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -6,7 +6,9 @@ import { Drawer, Stack, Menu, - Title + Title, + Flex, + Image, } from '@mantine/core'; import { useDisclosure } from '@mantine/hooks'; import { Link, useLocation } from 'react-router-dom'; @@ -177,17 +179,31 @@ export function Navbar() { return (
- - - Logo - openIPAM + + + + Logo + openIPAM + {items} - - - openIPAM - + + + + + Logo + + openIPAM + + + + { {editableObj && actions.length !== 0 && } {neededAttr.map(attr => ( - + {handleFormatHeader(attr)} {sortable && sortableFields?.includes(attr) && ( @@ -337,7 +338,7 @@ const PaginatedTable = (props: PaginatedTableProps): JSX.Element => { ))} {editableObj && editFunction && ( -
+ {isEditing ? ( <> @@ -373,7 +374,7 @@ const PaginatedTable = (props: PaginatedTableProps): JSX.Element => { )} -
+
)}
From 15e9f89294e341a8780ab76efd417a2b2df52cf2 Mon Sep 17 00:00:00 2001 From: Treyson Date: Tue, 13 Aug 2024 15:23:59 -0600 Subject: [PATCH 50/96] clean code --- src/App.tsx | 6 +++++- src/components/DomainGrid.tsx | 16 ++++++++++++++-- src/components/LoginForm.tsx | 1 - src/components/Navbar.tsx | 6 +++--- src/config.ts | 2 ++ src/pages/Domains.tsx | 25 ------------------------- src/routes.tsx | 2 -- src/utilities/apiFunctions.ts | 9 ++++++++- 8 files changed, 32 insertions(+), 35 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 6ec07d6..6df8dfe 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,6 +1,10 @@ import React from 'react'; import '@mantine/core/styles.css'; -import { MantineProvider, Container, ColorSchemeScript } from '@mantine/core'; +import { + MantineProvider, + Container, + ColorSchemeScript +} from '@mantine/core'; import { BrowserRouter, useRoutes } from 'react-router-dom'; import config from './config'; import { ConfigProvider } from './contexts/ConfigContext'; diff --git a/src/components/DomainGrid.tsx b/src/components/DomainGrid.tsx index 5d47800..0c9fa03 100644 --- a/src/components/DomainGrid.tsx +++ b/src/components/DomainGrid.tsx @@ -2,7 +2,18 @@ import { useState, useEffect } from 'react'; import { usePaginatedApi } from '../hooks/useApi'; import { useMediaQuery } from '@mantine/hooks'; import { getApiEndpointFunctions } from '../utilities/apiFunctions'; -import { Card, Text, Title, Grid, Paper, Badge, Group, ActionIcon, em, Pagination } from '@mantine/core'; +import { + Card, + Text, + Title, + Grid, + Paper, + Badge, + Group, + ActionIcon, + em, + Pagination +} from '@mantine/core'; import { FaEye } from "react-icons/fa"; import { Link } from 'react-router-dom'; @@ -74,12 +85,13 @@ const DomainGrid = (): JSX.Element => { bottom={"10px"} right={"10px"} > - + diff --git a/src/components/LoginForm.tsx b/src/components/LoginForm.tsx index 6378fab..c2d3666 100644 --- a/src/components/LoginForm.tsx +++ b/src/components/LoginForm.tsx @@ -33,7 +33,6 @@ const LoginForm = () => { await fetchCsrfToken(); if (isUser(loginResponse)) { const userDetailsResponse = await api.auth.me(); - console.log(userDetailsResponse); setUser({ ...loginResponse, is_ipamadmin: userDetailsResponse.is_ipamadmin, diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index 4239c93..15b8671 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -182,17 +182,17 @@ export function Navbar() { - Logo + openIPAM Logo openIPAM {items} - + - Logo + openIPAM Logo { const apiUrl = validateHostName(import.meta.env.VITE_API_URL as string); const frontendUrl = validateHostName(import.meta.env.VITE_FRONTEND_URL as string); +// Add more configuration options here const config: AppConfig = { apiUrl, diff --git a/src/pages/Domains.tsx b/src/pages/Domains.tsx index 7047b85..aeab338 100644 --- a/src/pages/Domains.tsx +++ b/src/pages/Domains.tsx @@ -1,34 +1,9 @@ -// import PaginatedTable from '../components/tables/PaginatedTable'; import DomainGrid from '../components/DomainGrid'; -// import { useState } from 'react'; -// import { getApiEndpointFunctions } from '../utilities/apiFunctions'; -// import { useAuth } from '../contexts/AuthContext'; const Domains = () => { - // const [pageSize] = useState<number>(10); - // const api = getApiEndpointFunctions(); - // const auth = useAuth(); - // if (auth.isAdmin()) { - // //paginated table cant handle the links for the domains. fix l8er - // return ( - // <> - // <PaginatedTable - // defPageSize={pageSize} - // getFunction={api.domain.get} - // title='Domains' - // neededAttr={['id', 'name', 'record_count']} - // morePageSizes={['1']} - // sortable={true} - // searchable={true} - // searchableFields={['name', 'dns_view', 'dns_type']} - // /> - // </> - // ) - // } else { return ( <DomainGrid /> ) - // } }; export default Domains; \ No newline at end of file diff --git a/src/routes.tsx b/src/routes.tsx index a2ec677..0204a69 100644 --- a/src/routes.tsx +++ b/src/routes.tsx @@ -1,4 +1,3 @@ -import { Domain } from 'domain'; import React from 'react'; import { RouteObject } from 'react-router-dom'; @@ -23,7 +22,6 @@ const routes: RouteObject[] = [ element: <DomainDetail /> }, { - path: '/admin', element: <AdminProtectedRoute />, children: [ diff --git a/src/utilities/apiFunctions.ts b/src/utilities/apiFunctions.ts index 2e139ed..88211c6 100644 --- a/src/utilities/apiFunctions.ts +++ b/src/utilities/apiFunctions.ts @@ -179,8 +179,15 @@ export const getApiEndpointFunctions = < API.PaginationParams<API.Filters.LogFilter>, API.PaginatedData<API.LogEntry> | StrictTypeChecking >(HttpMethod.GET, 'domains/', { headers: { 'Content-Type': 'application/json' } }), - + /** + * API endpoints for a specific Domain Object + * @param id + * @returns An object containing all endpoints for the given Domain object + */ byId: (id: string | number) => ({ + /** + * Gets DNSRecord objects connected to a Domain object + */ getRecords: requestGenerator< HttpMethod.GET, API.PaginationParams<API.Filters.LogFilter>, From 8878242fc6bfb2ff7d7ecd8e6597ce6be2b259d1 Mon Sep 17 00:00:00 2001 From: Treyson <treyson.grange@outlook.com> Date: Wed, 14 Aug 2024 11:17:57 -0600 Subject: [PATCH 51/96] PaginatedTable documentation --- src/components/dashboard/Actions.tsx | 2 + src/components/tables/PaginatedTable.tsx | 94 +++++++++++++++++------- 2 files changed, 71 insertions(+), 25 deletions(-) diff --git a/src/components/dashboard/Actions.tsx b/src/components/dashboard/Actions.tsx index 308f443..efac3dd 100644 --- a/src/components/dashboard/Actions.tsx +++ b/src/components/dashboard/Actions.tsx @@ -12,6 +12,7 @@ const Actions = (): JSX.Element => { neededAttr={['content_type', 'object_repr', 'action_time']} morePageSizes={['1']} sortable={true} + sortableFields={['content_type', 'object_repr', 'action_time']} /> <PaginatedTable defPageSize={5} @@ -21,6 +22,7 @@ const Actions = (): JSX.Element => { editableObj={true} actions={['Change Source Internal', 'Change Source LDAP']} sortable={true} + sortableFields={['name', 'id', 'permissions']} actionFunctions={{ ChangeSourceInternal: { func: (id: number) => console.log(`Editing group ${id}`), key: 'id' }, ChangeSourceLDAP: { func: (id: number) => console.log(`Deleting group ${id}`), key: 'id' } diff --git a/src/components/tables/PaginatedTable.tsx b/src/components/tables/PaginatedTable.tsx index 6934d25..cad5e3d 100644 --- a/src/components/tables/PaginatedTable.tsx +++ b/src/components/tables/PaginatedTable.tsx @@ -32,19 +32,19 @@ import { interface BasePaginatedTableProps { /** - * The function to call to get the data for the table. + * The GET function defined in /utilities/apiFunctions.ts used to get the data. */ getFunction: QueryRequest<any, PaginatedData<unknown>>; /** - * The default page size to use for the table. + * The default page size to use for the table. Defaults to 5. */ - defPageSize: number; + defPageSize?: number; /** * The title of the table. */ title: string; /** - * The attributes needed to display in the table. + * The attributes from the API to display in the table. */ neededAttr: string[]; /** @@ -58,25 +58,41 @@ interface BasePaginatedTableProps { overridePageSizes?: boolean; /** * Whether the objects in the table are sortable. + * Sortability depends on API support. In the Django API, you must alter the + * View or viewset. In the filter_queryset function, we will get queryparams; + * `order_by` and `direction` and sort the queryset accordingly. + * + * For a quick example, see openipam/api_v2/views/logs.py, lines 47-55. */ sortable?: boolean; /** * The fields that are sortable. + * Most fields will end up being sortable if the API is set up correctly, but + * proceed with caution. */ sortableFields?: string[]; /** * Whether the objects in the table are searchable * Requires `searchableFields` to be set. + * Searchability depends on API support. In the Django API, you must alter the + * View. To do so, you must obtain the query parameters for the searchable fields, and + * filter the queryset accordingly. + * + * For a quick example, see openipam/api_v2/views/users, lines 60-72. */ searchable?: boolean; /** * The fields that are searchable. * Requires `searchable` to be set. + * All searchable fields need to be set up in the API. They won't just work. */ searchableFields?: string[]; /** * The additional URL parameters to pass to the API. * Pass it in like *additionalUrlParams={{ "paramName": String(value)) }}* + * + * There are no current use cases for this. But it can be used to pass in additional + * parameters to the get function of the API. */ additionalUrlParams?: Record<string, string>; } @@ -84,6 +100,10 @@ interface BasePaginatedTableProps { interface EditablePaginatedTableProps extends BasePaginatedTableProps { /** * Whether the objects in the table are editable. + * + * DO not set this to true if you dont have at least one of the following: + * - 'actions' and 'actionFunctions' + * - 'editFunction' and 'editableFields' */ editableObj: true; /** @@ -95,11 +115,15 @@ interface EditablePaginatedTableProps extends BasePaginatedTableProps { * The functions to call when an action is performed. * To have actions, both `actions` and `actionFunctions` must be set. * Please pass func and key, where the key is the attribute to pass to the function. + * functionName: { func: (id: number) => console.log(`Editing group ${id}`), key: 'id' }, + * OR + * functionName: { func: previouslyDefinedFunction, key: 'id' } */ actionFunctions?: Record<string, { func: (params: any) => void, key: string }>; /** - * The function to call to edit an object in the table. - * Requires `editableObj` to be + * The PUT function defined in /utilities/apiFunctions.ts used to edit the data. + * The way you pass the function is a bit different. See the example in DomainDetail.tsx. + * Requires `editableObj` to be true. */ editFunction?: (dnsName: string) => QueryRequest<any, any>; /** @@ -117,6 +141,25 @@ interface NonEditablePaginatedTableProps extends BasePaginatedTableProps { type PaginatedTableProps = EditablePaginatedTableProps | NonEditablePaginatedTableProps; +/** + * The PaginatedTable component is a flexible and reusable component + * that displays paginated data in a table format. It supports sorting, actions, editing, + * searching, page size selection, and more. + * + * To properly use most features in this component, you must pass in the necessary props. + * For example, if you want to use sorting, you cannot just set sortable to true. You + * must also state what fields are sortable in the sortableFields prop. This is because + * the API may not support sorting on all fields. + * + * If you want to use searching, you must set searchable to true and pass in the fields + * that are searchable in the searchableFields prop. See the props documentation for more. + * + * If you want to use actions, you must pass in the actions and actionFunctions props. + * For more information, see the props documentation above. + * + * If you want to use editing, you must pass in the editFunction and editableFields props. + * For more information, see the props documentation above. + */ const PaginatedTable = (props: PaginatedTableProps): JSX.Element => { const actions = (props as EditablePaginatedTableProps).actions ?? []; const actionFunctions = (props as EditablePaginatedTableProps).actionFunctions ?? {}; @@ -138,7 +181,7 @@ const PaginatedTable = (props: PaginatedTableProps): JSX.Element => { } = props; const [data, setData] = useState<any[]>([]); const [maxPages, setMaxPages] = useState<number>(0); - const [pageSize, setPageSize] = useState<number>(defPageSize); + const [pageSize, setPageSize] = useState<number>(defPageSize || 5); const [page, setPage] = useState<number>(1); const [selectedObjs, setSelectedObjs] = useState<Set<any>>(new Set()); const [notification, setNotification] = useState<string[] | null>(null); @@ -149,6 +192,17 @@ const PaginatedTable = (props: PaginatedTableProps): JSX.Element => { const [editingRow, setEditingRow] = useState<number | null>(null); const [editValues, setEditValues] = useState<Record<string, string>>({}); + const { data: paginatedData, loading } = usePaginatedApi( + getFunction, + page, + pageSize, + { + ...orderBy ? { 'order_by': orderBy, 'direction': direction } : {}, + ...Object.fromEntries(Object.entries(searchTerms).filter(([_, v]) => v)), + ...additionalUrlParams + } + ); + const pageSizes = ['5', '10', '20']; if (overridePageSizes) { pageSizes.length = 0; } @@ -167,17 +221,6 @@ const PaginatedTable = (props: PaginatedTableProps): JSX.Element => { setSearchTerms(prevTerms => ({ ...prevTerms, [field]: value })); }; - const { data: paginatedData } = usePaginatedApi( - getFunction, - page, - pageSize, - { - ...orderBy ? { 'order_by': orderBy, 'direction': direction } : {}, - ...Object.fromEntries(Object.entries(searchTerms).filter(([_, v]) => v)), - ...additionalUrlParams - } - ); - const handleActionChange = (value: string | null) => { if (value) { setAction(value); @@ -250,11 +293,10 @@ const PaginatedTable = (props: PaginatedTableProps): JSX.Element => { setEditingRow(null); } } catch (error) { - setNotification([`Error: ${error}`, 'Error']); + setNotification([`${error}`, 'Error']); } }; - useEffect(() => { if (paginatedData && paginatedData.results) { setData(paginatedData.results); @@ -286,9 +328,9 @@ const PaginatedTable = (props: PaginatedTableProps): JSX.Element => { <Group gap='xs'> <Text>{handleFormatHeader(attr)}</Text> {sortable && sortableFields?.includes(attr) && ( - <Button variant='subtle' onClick={() => handleSort(attr, direction)}> + <ActionIcon variant='subtle' onClick={() => handleSort(attr, direction)}> {orderBy === attr ? (direction === 'asc' ? <FaSortUp /> : <FaSortDown />) : <FaSort />} - </Button> + </ActionIcon> )} {searchable && searchableFields?.includes(attr) && ( <TextInput @@ -308,7 +350,9 @@ const PaginatedTable = (props: PaginatedTableProps): JSX.Element => { {data.length === 0 ? ( <Table.Tr> <Table.Td colSpan={neededAttr.length + (editableObj ? 1 : 0)}> - <Text size='xl'>No data available</Text> + <Text size='xl'> + {loading ? 'Loading...' : data.length === 0 ? 'No data available' : null} + </Text> </Table.Td> </Table.Tr> ) : ( @@ -408,7 +452,7 @@ const PaginatedTable = (props: PaginatedTableProps): JSX.Element => { <Notification title={notification?.[0]} icon={notification?.[1] === 'Success' ? <FaRegCircleCheck /> : <FaRegCircleXmark />} - color='blue' + color={notification?.[1] === 'Success' ? 'green' : 'red'} onClose={() => setNotification(null)} /> </Dialog> @@ -418,7 +462,7 @@ const PaginatedTable = (props: PaginatedTableProps): JSX.Element => { <Button disabled={selectedObjs.size === 0} onClick={() => setSelectedObjs(new Set())} color='#8d0d20'> Clear Selection {selectedObjs.size !== 0 && `(${selectedObjs.size})`} </Button> - <Button onClick={submitChange} color='blue'> + <Button onClick={submitChange} disabled={selectedObjs.size === 0} color='blue'> Submit </Button> </div> From ef2a0c31005c0ec452c9b0ff101e9f7e4e6a376c Mon Sep 17 00:00:00 2001 From: Treyson <treyson.grange@outlook.com> Date: Thu, 15 Aug 2024 13:05:08 -0600 Subject: [PATCH 52/96] Started work on dashboard rework --- src/components/DomainGrid.tsx | 39 +++++-------- src/components/dashboard/Actions.tsx | 10 ++-- src/components/dashboard/Admin.tsx | 53 ------------------ src/components/dashboard/Stats.tsx | 71 +++++++++++++++--------- src/components/dashboard/UserHosts.tsx | 17 ++++++ src/components/dashboard/Welcome.tsx | 14 ++--- src/components/dashboard/index.tsx | 15 +++-- src/components/tables/PaginatedTable.tsx | 7 ++- src/utilities/apiFunctions.ts | 10 ++++ 9 files changed, 108 insertions(+), 128 deletions(-) delete mode 100644 src/components/dashboard/Admin.tsx create mode 100644 src/components/dashboard/UserHosts.tsx diff --git a/src/components/DomainGrid.tsx b/src/components/DomainGrid.tsx index 0c9fa03..2436947 100644 --- a/src/components/DomainGrid.tsx +++ b/src/components/DomainGrid.tsx @@ -1,6 +1,5 @@ import { useState, useEffect } from 'react'; import { usePaginatedApi } from '../hooks/useApi'; -import { useMediaQuery } from '@mantine/hooks'; import { getApiEndpointFunctions } from '../utilities/apiFunctions'; import { Card, @@ -11,8 +10,7 @@ import { Badge, Group, ActionIcon, - em, - Pagination + Pagination, } from '@mantine/core'; import { FaEye } from "react-icons/fa"; import { Link } from 'react-router-dom'; @@ -36,17 +34,6 @@ const DomainGrid = (): JSX.Element => { } }, [data]); - const calculateSpan = () => { - const isMobile = useMediaQuery(`(max-width: ${em(1100)})`); - if (isMobile) { - return 12; - } - return 3; - }; - - - const span = calculateSpan(); - return ( <Paper radius='lg' p='lg' m='lg' withBorder> <Group justify='space-between'> @@ -65,10 +52,15 @@ const DomainGrid = (): JSX.Element => { /> )} </Group> - <Grid justify="flex-start" align="stretch" mt="lg"> + <Grid mt="lg" gutter="lg"> {data?.results?.map((domain: any, index: number) => ( - <Grid.Col span={span} key={index}> - <Card radius="lg" h={"12rem"} shadow="xl" padding="lg"> + <Grid.Col span={{ xs: 12, sm: 12, md: 6, lg: 4, xl: 3 }} key={index}> + <Card + radius="lg" + h="12rem" + shadow="xl" + padding="lg" + > <Group> <Link to={`/domains/${domain.name}`} className='header-link'> <Title order={2} size="h3">{domain.name} @@ -77,21 +69,17 @@ const DomainGrid = (): JSX.Element => { {domain.record_count} - + {domain.description} - - + + @@ -101,6 +89,7 @@ const DomainGrid = (): JSX.Element => { ))} + ); }; diff --git a/src/components/dashboard/Actions.tsx b/src/components/dashboard/Actions.tsx index efac3dd..620c014 100644 --- a/src/components/dashboard/Actions.tsx +++ b/src/components/dashboard/Actions.tsx @@ -6,7 +6,6 @@ const Actions = (): JSX.Element => { return ( <> { sortable={true} sortableFields={['content_type', 'object_repr', 'action_time']} /> - console.log(`Editing group ${id}`), key: 'id' }, ChangeSourceLDAP: { func: (id: number) => console.log(`Deleting group ${id}`), key: 'id' } }} searchable={true} searchableFields={['name']} - /> + /> */} ); } diff --git a/src/components/dashboard/Admin.tsx b/src/components/dashboard/Admin.tsx deleted file mode 100644 index dd96da2..0000000 --- a/src/components/dashboard/Admin.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { Text, Paper, Title, Table } from '@mantine/core'; -import { useNavigate } from 'react-router-dom'; - -const Admin = () => { - const navigate = useNavigate(); - const items = { - 'Administration': 'admin', - 'Authentication and Authorization': 'authentication', - 'Auth': 'auth', - 'Token': 'token', - 'Core': 'core', - 'Dns': 'dns', - 'Guardian': 'guardian', - 'Hosts': 'hosts', - 'Log': 'log', - 'Network': 'network', - 'Taggit': 'taggit', - 'User': 'user', - }; - - return ( - - Admin - These links are broken on my prod, all the links lead to the home page :/ - - - {Object.entries(items).map(([label, link]) => ( - - - - navigate(link)} - onMouseEnter={(e) => (e.target as HTMLAnchorElement).style.textDecoration = 'underline'} - onMouseLeave={(e) => (e.target as HTMLAnchorElement).style.textDecoration = 'none'} - > - {label} - - - - - ))} - -
-
- ); -}; - -export default Admin; diff --git a/src/components/dashboard/Stats.tsx b/src/components/dashboard/Stats.tsx index 61236e0..06bc251 100644 --- a/src/components/dashboard/Stats.tsx +++ b/src/components/dashboard/Stats.tsx @@ -1,17 +1,19 @@ import { useEffect, useState } from 'react'; import { useApiData } from '../../hooks/useApi'; -import { Paper, Title } from '@mantine/core'; +import { Paper, Title, Card, Flex, Text, Grid } from '@mantine/core'; const Stats = () => { const [data, setData] = useState([]); const { data: apiData, loading, error } = useApiData(api.reports.recent); const hostItems = ['hosts_today', 'hosts_week', 'hosts_month']; const userItems = ['users_today', 'users_week', 'users_month']; + const dnsItems = ['dns_today', 'dns_week', 'dns_month']; const itemText = (item: string) => - item.startsWith('hosts') ? item.replace('hosts_', item.includes('today') ? 'Hosts changed ' : 'Hosts changed this ') : - item.startsWith('users') ? item.replace('users_', item.includes('today') ? 'Users joined ' : 'Users joined this ') : - item; + item.startsWith('hosts') ? item.replace('hosts_', item.includes('today') ? 'Changed ' : 'Changed this ') : + item.startsWith('users') ? item.replace('users_', item.includes('today') ? 'Joined ' : 'Joined this ') : + item.startsWith('dns') ? item.replace('dns_', item.includes('today') ? 'Changed ' : 'Changed this ') : + item; useEffect(() => { @@ -22,31 +24,48 @@ const Stats = () => { return ( - Stats - - {loading &&

Loading...

} - {error &&

Error: {error.message}

} + Recent Stats + {loading && !error && Loading...} + {error && Error: {error.message}} {data && ( - <> - - Hosts - {hostItems.map((item) => ( -

- {data[item]} {itemText(item)} -

- ))} -
- - Users - {userItems.map((item) => ( -

- {data[item]} {itemText(item)} -

- ))} -
- + + + + Hosts + {hostItems.map((item) => ( + + {itemText(item)} + {data[item]} + + ))} + + + + + Users + {userItems.map((item) => ( + + {itemText(item)} + {data[item]} + + ))} + + + + + DNS Records + {dnsItems.map((item) => ( + + {itemText(item)} + {data[item]} + + ))} + + + )}
+ ); }; diff --git a/src/components/dashboard/UserHosts.tsx b/src/components/dashboard/UserHosts.tsx new file mode 100644 index 0000000..311a125 --- /dev/null +++ b/src/components/dashboard/UserHosts.tsx @@ -0,0 +1,17 @@ +import PaginatedTable from '../tables/PaginatedTable'; +import { getApiEndpointFunctions } from '../../utilities/apiFunctions'; + +const UserHosts = () => { + const api = getApiEndpointFunctions(); + + return ( + + ); +} +export default UserHosts; \ No newline at end of file diff --git a/src/components/dashboard/Welcome.tsx b/src/components/dashboard/Welcome.tsx index 886f312..1000670 100644 --- a/src/components/dashboard/Welcome.tsx +++ b/src/components/dashboard/Welcome.tsx @@ -1,4 +1,4 @@ -import { Paper, Title, Button } from '@mantine/core'; +import { Paper, Title, Button, Text, Group } from '@mantine/core'; import { useNavigate } from 'react-router-dom'; import { getApiEndpointFunctions } from '../../utilities/apiFunctions'; import { useAuth } from '../../contexts/AuthContext'; @@ -21,14 +21,10 @@ const Welcome = () => { return ( Welcome to openIPAM -

We are now using Issues on GitHub to help aid us with features and bugs. Please make an issue on GitHub to give us feedback.

-

Item to consider when using the new interface:

-
    -
  • Permissions - Do you have all your permissions?
  • -
  • Hosts - Do you see all your hosts?
  • -
  • DNS Entries - Do you see all DNS Entries?
  • -
-

If you have any questions, please email: openipam@lists.usu.edu

+ We ask for your patience as we migrate over to our new upgraded design and implementation of openIPAM + + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. +
); diff --git a/src/components/dashboard/index.tsx b/src/components/dashboard/index.tsx index 09a0464..34277c2 100644 --- a/src/components/dashboard/index.tsx +++ b/src/components/dashboard/index.tsx @@ -3,13 +3,12 @@ import { Grid } from '@mantine/core'; import { useNavigate } from 'react-router-dom'; import { useAuth } from '../../contexts/AuthContext'; import Actions from './Actions'; -import Admin from './Admin'; -import Navigation from './Navigation'; import Stats from './Stats'; import Welcome from './Welcome'; +import UserHosts from './UserHosts'; const DashBoard = () => { - const { user, isAdmin } = useAuth(); + const { user } = useAuth(); const navigate = useNavigate(); useEffect(() => { @@ -24,14 +23,14 @@ const DashBoard = () => { return ( - + - - {isAdmin() && } + + - + - + ); diff --git a/src/components/tables/PaginatedTable.tsx b/src/components/tables/PaginatedTable.tsx index cad5e3d..e887ad4 100644 --- a/src/components/tables/PaginatedTable.tsx +++ b/src/components/tables/PaginatedTable.tsx @@ -43,6 +43,10 @@ interface BasePaginatedTableProps { * The title of the table. */ title: string; + /** + * + */ + noDataMessage?: string; /** * The attributes from the API to display in the table. */ @@ -180,6 +184,7 @@ const PaginatedTable = (props: PaginatedTableProps): JSX.Element => { additionalUrlParams } = props; const [data, setData] = useState([]); + const [noDataMessage] = useState(props.noDataMessage || "No data found"); const [maxPages, setMaxPages] = useState(0); const [pageSize, setPageSize] = useState(defPageSize || 5); const [page, setPage] = useState(1); @@ -351,7 +356,7 @@ const PaginatedTable = (props: PaginatedTableProps): JSX.Element => { - {loading ? 'Loading...' : data.length === 0 ? 'No data available' : null} + {loading ? 'Loading...' : data.length === 0 ? noDataMessage : null} diff --git a/src/utilities/apiFunctions.ts b/src/utilities/apiFunctions.ts index 88211c6..eb969ae 100644 --- a/src/utilities/apiFunctions.ts +++ b/src/utilities/apiFunctions.ts @@ -121,6 +121,16 @@ export const getApiEndpointFunctions = < API.PaginatedData | StrictTypeChecking >(HttpMethod.GET, 'groups/', { headers: { 'Content-Type': 'application/json' } }), }, + /** + * Hosts API + */ + hosts: { + myhosts: requestGenerator< + HttpMethod.GET, + API.PaginationParams, + API.PaginatedData | StrictTypeChecking + >(HttpMethod.GET, 'hosts/mine/', { headers: { 'Content-Type': 'application/json' } }), + }, /** * DNS API */ From be2193d97139ab2a05b11c5232d4149141c5e090 Mon Sep 17 00:00:00 2001 From: Treyson Date: Thu, 15 Aug 2024 13:40:22 -0600 Subject: [PATCH 53/96] clean clean clean --- src/components/DomainGrid.tsx | 4 +- src/components/Navbar.tsx | 3 +- src/components/dashboard/Actions.tsx | 15 - src/components/dashboard/Navigation.tsx | 44 --- src/components/dashboard/Welcome.tsx | 2 +- src/components/dashboard/index.tsx | 1 - src/contexts/AuthContext.tsx | 8 +- src/hooks/useApi.ts | 8 +- src/types/api.ts | 381 ++++++------------------ src/types/apiFilters.ts | 114 ------- src/utilities/getCookie.ts | 6 - 11 files changed, 112 insertions(+), 474 deletions(-) delete mode 100644 src/components/dashboard/Navigation.tsx diff --git a/src/components/DomainGrid.tsx b/src/components/DomainGrid.tsx index 2436947..75e3ec1 100644 --- a/src/components/DomainGrid.tsx +++ b/src/components/DomainGrid.tsx @@ -1,6 +1,8 @@ import { useState, useEffect } from 'react'; import { usePaginatedApi } from '../hooks/useApi'; import { getApiEndpointFunctions } from '../utilities/apiFunctions'; +import { FaEye } from "react-icons/fa"; +import { Link } from 'react-router-dom'; import { Card, Text, @@ -12,8 +14,6 @@ import { ActionIcon, Pagination, } from '@mantine/core'; -import { FaEye } from "react-icons/fa"; -import { Link } from 'react-router-dom'; const DomainGrid = (): JSX.Element => { diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index 15b8671..c2872d9 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -34,7 +34,8 @@ const links: DropdownLink[] = [ ]; /** - * Links for admin users. + * Links for admin users. A LOT of these are gonna get nuked, this is just copy paste from + * live openipam :/ */ const adminLinks: DropdownLink[] = [ { link: '/', label: 'Home' }, diff --git a/src/components/dashboard/Actions.tsx b/src/components/dashboard/Actions.tsx index 620c014..4215e42 100644 --- a/src/components/dashboard/Actions.tsx +++ b/src/components/dashboard/Actions.tsx @@ -13,21 +13,6 @@ const Actions = (): JSX.Element => { sortable={true} sortableFields={['content_type', 'object_repr', 'action_time']} /> - {/* console.log(`Editing group ${id}`), key: 'id' }, - ChangeSourceLDAP: { func: (id: number) => console.log(`Deleting group ${id}`), key: 'id' } - }} - searchable={true} - searchableFields={['name']} - /> */} ); } diff --git a/src/components/dashboard/Navigation.tsx b/src/components/dashboard/Navigation.tsx deleted file mode 100644 index 6735796..0000000 --- a/src/components/dashboard/Navigation.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { Text, Paper, Title, Table } from '@mantine/core'; -import { useNavigate } from 'react-router-dom'; - -const Navigation = () => { - const navigate = useNavigate(); - const items = { - 'List Hosts': 'hosts', - 'Add Host': 'hostsadd', - 'DNS Records': 'dns', - 'Feature or Bug?': 'request', - 'Profile': 'profile', - } - return ( - - Navigation - - - {Object.entries(items).map(([label, link]) => ( - - - - navigate(link)} - onMouseEnter={(e) => (e.target as HTMLAnchorElement).style.textDecoration = 'underline'} - onMouseLeave={(e) => (e.target as HTMLAnchorElement).style.textDecoration = 'none'} - > - {label} - - - - - ))} - -
-
- ); -}; - -export default Navigation; \ No newline at end of file diff --git a/src/components/dashboard/Welcome.tsx b/src/components/dashboard/Welcome.tsx index 1000670..937667e 100644 --- a/src/components/dashboard/Welcome.tsx +++ b/src/components/dashboard/Welcome.tsx @@ -1,4 +1,4 @@ -import { Paper, Title, Button, Text, Group } from '@mantine/core'; +import { Paper, Title, Button, Text } from '@mantine/core'; import { useNavigate } from 'react-router-dom'; import { getApiEndpointFunctions } from '../../utilities/apiFunctions'; import { useAuth } from '../../contexts/AuthContext'; diff --git a/src/components/dashboard/index.tsx b/src/components/dashboard/index.tsx index 34277c2..f6cc4df 100644 --- a/src/components/dashboard/index.tsx +++ b/src/components/dashboard/index.tsx @@ -26,7 +26,6 @@ const DashBoard = () => { - diff --git a/src/contexts/AuthContext.tsx b/src/contexts/AuthContext.tsx index 3343d96..259a9cc 100644 --- a/src/contexts/AuthContext.tsx +++ b/src/contexts/AuthContext.tsx @@ -1,4 +1,10 @@ -import React, { createContext, useState, useContext, ReactNode, useEffect } from 'react'; +import React, { + createContext, + useState, + useContext, + ReactNode, + useEffect +} from 'react'; interface User { username: string; diff --git a/src/hooks/useApi.ts b/src/hooks/useApi.ts index bae3b62..46d4337 100644 --- a/src/hooks/useApi.ts +++ b/src/hooks/useApi.ts @@ -47,7 +47,7 @@ const useApiEndpointMemo = (endpoint: MemoizableRequest) => { }; /** - * Fetch JSON data from a KCM API endpoint with query parameters + * Fetch JSON data from a openIPAM API V2 endpoint with query parameters * @param endpoint * @param queryParams The optional query parameters to include in the requestThe API endpoint function to fetch data from * @param transform A function to transform the fetched data before returning it, called once after the request completes @@ -157,7 +157,7 @@ export const useApiData = < }; /** - * Fetch paginated JSON data from a KCM API endpoint with query parameters + * Fetch paginated JSON data from a openIPAM API V2 endpoint with query parameters * @param endpoint The API endpoint function to fetch data from * @param page The page number to fetch * @param pageSize The number of items per page @@ -188,7 +188,7 @@ export const usePaginatedApi = < }; /** - * Fetch JSON data from a KCM API list endpoint and cache the results. Uses a LRU cache to store the last `cacheSize` pages. + * Fetch JSON data from a openIPAM API V2 list endpoint and cache the results. Uses a LRU cache to store the last `cacheSize` pages. * @param endpoint The API endpoint function to fetch data from * @param prefetch How many pages to 'prefetch' (fetch in advance) when the page changes * @param page The page number to fetch @@ -352,7 +352,7 @@ export const useCachingApi = < }; /** - * Fetch JSON data from a KCM API endpoint and allow the user to edit it. The data will be saved back to the API when the user clicks 'Save'. + * Fetch JSON data from a openIPAM API V2 endpoint and allow the user to edit it. The data will be saved back to the API when the user clicks 'Save'. * @param getEndpoint The API endpoint function to fetch data from. If null, the initial data object will be used instead. * @param setEndpoint The API endpoint function to save data to * @param initialData The initial data object to use if the fetch fails diff --git a/src/types/api.ts b/src/types/api.ts index df3b369..097a292 100644 --- a/src/types/api.ts +++ b/src/types/api.ts @@ -1,4 +1,4 @@ -// Type definitions for the KCM API. +// Type definitions for the openIPAM API. // Includes generic container types for API responses, as well as specific // types for each endpoint. The specific types are accepted by POST requests, // and the Response types are returned by GET requests and contain the ID field. @@ -6,7 +6,6 @@ // types, and return the full Response type. import { KeyofStorable } from '.'; -import { ISO8601String } from '../utilities/dateUtils'; import * as Filters from './apiFilters'; export { Filters }; @@ -27,16 +26,49 @@ export type GenericResponse = { }; export type AuthResponse = { + /** + * ID of the user. + */ id: number; + /** + * The username of the user. + */ username: string; + /** + * The first name of the user. + */ first_name: string; + /** + * The last name of the user. + */ last_name: string; + /** + * The email address of the user. + */ email: string; + /** + * Whether the user is a staff member. + */ is_staff: boolean; + /** + * Whether the user is a superuser. + */ is_superuser: boolean; + /** + * Whether the user is an IPAM admin. + */ is_ipamadmin: boolean; + /** + * Whether the user is active. + */ is_active: boolean; + /** + * The date the user last logged in. + */ last_login: string; + /** + * Groups the user is a member of. + */ groups: string[]; } @@ -171,22 +203,9 @@ export type PaginatedData = { results: StoredObject[]; }; -/** - * The data that can be passed to an update-like endpoint. - */ -export type PatchData = Partial< - StoredObject ->; - -export interface MultipleSelection { - /** - * The IDs of the objects to be operated on. - */ - selected: number[]; -} /** - * Interface for the User model. + * Interface for the User model. Not sure this is needed, from KCM, but its used below */ export interface User { /** @@ -221,6 +240,11 @@ export interface User { * this field will be silently ignored. */ is_superuser: boolean; + /** + * Whether the user is an IPAM admin. When a user is updating their own profile, + * this field will be silently ignored. + */ + is_ipamadmin: boolean; /** * The date of the user's last login, in ISO format. */ @@ -262,335 +286,122 @@ export interface DNSRecord { url: string; } -/** - * Interface for the Filter model. - */ -export interface SavedFilter { - /** - * The name of the filter. - */ - name: string; - /** - * The slug of the filter. This is a unique identifier for the filter. - */ - slug: string; - /** - * The parameters of the filter, in JSON format. - */ - json_string: string; -} -export interface Device { - /** - * The hostname of the device. - */ - host: string; - /** - * The model of the device, if known. - */ - model: string | null; - /** - * The firmware version of the device, if known. - */ - version: string | null; - /** - * The vendor of the device. - */ - vendor: string; - /** - * The location of the device, if known. - */ - location: string | null; - /** - * The serial number of the device, if known. - */ - serial: string | null; - /** - * The asset tag of the device. - */ - asset: string; - /** - * Whether the device is reachable via SSH. - */ - ssh: boolean; - /** - * The firmware version loaded on the primary flash of the device, if known. - */ - primary_flash: string | null; - /** - * The firmware version loaded on the secondary flash of the device, if known. - */ - secondary_flash: string | null; - /** - * The IP address of the device, if any. - */ - ip: string | null; - /** - * Timestamp of the last successful update, in ISO format. - */ - lastupdate: string; - /** - * Whether the last update was successful. - */ - lastupdatesuccess: boolean; - /** - * The error message of the last update, if any. - */ - lastupdateerror: string | null; - /** - * The time of a pending scheduled reload, in ISO format. - * May be null if no reload is scheduled. - */ - reloadat: ISO8601String | null; -} -/** - * Interface for the Job model. - */ -export interface Job { - /** - * The time the job is scheduled to run, in ISO format. - */ - scheduled_time: ISO8601String; - /** - * The command to run on the device. Multi-line string, should be - * rendered as a code block. - */ - command: string; - /** - * If true, the job will be run on devices concurrently. Defaults to true. - */ - concurrency?: boolean; - /** - * If true, devices will be rediscovered after the job is run. Defaults to - * false. - */ - rediscover?: boolean; +export interface LogEntry { /** - * Integer timeout for the job. + * The ID of the job this log entry is associated with. */ - timeout: number; + job: number; /** - * The device IDs to run the job on. + * The ID of the device this log entry is associated with. */ - devices: number[]; -} - -export interface JobAdditionalFields { + device: number; /** - * The state of the job. A value of 0 indicates success, other values indicate - * errors. + * The timestamp of the log entry, in ISO format. */ - state: number; + stamp: string; /** - * The number of devices that succeeded in running the job. Only present on returned - * objects, do not pass this field to the API. + * The severity of the log entry. */ - success: number; + severity: number; /** - * The number of devices that failed to run the job. Only present on returned - * objects, do not pass this field to the API. + * The message of the log entry. */ - failed: number; -} - -/** - * Interface for the list websocket action's data array. - */ -export interface JobListData { - id: number; - scheduled_time: string; - progress: { - pending: number; - running: number; - failed: number; - success: number; - }; - state: number; - tasks: StoredObject[]; - devices: number[]; - url: string; + message: string; } -/** - * Interface for the JobStatus model, which is read-only. - */ -export interface JobStatus { - /** - * The job start timestamp, in ISO format. May be null if the job has not - * started yet. - */ - start_time: string | null; - /** - * The job end timestamp, in ISO format. May be null if the job has not - * finished yet. - */ - end_time: string | null; - /** - * Whether the job was successful. Will be false if the job has not finished - * yet. - */ - success: boolean; - /** - * The hostname of the device the job was run on. - */ - host: string; +export interface RecentReport { /** - * The output of the job. Multi-line string, should be rendered as a code - * block. + * How many hosts were edited today. */ - output: string; + hosts_today: number; /** - * The ID of the device the job was run on. + * How many hosts were edited this week. */ - device_id: number; -} - -/** - * File list used by the firmware list endpoint. - */ -export interface FileList { + hosts_week: number; /** - * A list of files. Each file is a tuple of the filename and the name of the - * firmware. Also includes a special value for the 'Select a File' option. + * How many hosts were edited this month. */ - files: ([string, string] | '' | 'Select a File')[]; -} - -/** - * Interface for the Firmware model. - */ -export interface Firmware { + hosts_month: number; /** - * The filename of the firmware, relative to the firmware directory on the - * server. + * How many users were created today. */ - filename: string; + users_today: number; /** - * The name of the firmware, as displayed to the user. + * How many users were created this week. */ - name: string; + users_week: number; /** - * Ordering of the firmware. Higher values are displayed first. + * How many users were created this month. */ - ordering?: number; -} - -/** - * Interface for the FirmwareUpdate endpoint. - */ -export interface FirmwareUpdateArguments { + users_month: number; /** - * The IDs of the devices to update. + * How many DNS records were edited today. */ - devices: number[]; + dns_today: number; /** - * The ID of the firmware to update to. + * How many DNS records were edited this week. */ - firmware: number; + dns_week: number; /** - * The time the update is scheduled to run, in ISO format. + * How many DNS records were edited this month. */ - scheduled_time: string; + dns_month: number; } -/** - * Interface for the DiscoverGroup model. - */ -export interface DiscoverGroup { - /** - * The name of the OpenIPAM group this DiscoverGroup is associated with. - */ - ipamgroup: string; - /** - * The username to use when logging into devices. - */ - username: string; +export interface Host { /** - * The password to use when logging into devices. + * The MAC address of the host. */ - password: string; + mac: string; /** - * The enable password to use when logging into devices. May be null. + * The API URL of the host. */ - enablepassword: string | null; -} - -export interface ConfigDiff { + details: string; /** - * The diff string + * The vendor of the host. */ - diff: string; + vendor: string; /** - * Added lines + * The hostname of the host. */ - added: string[]; + hostname: string; /** - * Removed lines + * The expiration date of the host, in ISO format. */ - removed: string[]; -} - -export interface ConfigVersion { + expires: string; /** - * The date the config was created, in ISO format. + * The optional description of the host. */ - date_created: string; + description: string; /** - * The comment included with this revision. + * Whether or not the host is dynamic. */ - comment: string; + is_dynamic: boolean; /** - * The configuration file. + * If the host is disabled, this will include 3 fields, 'reason', 'changed', and 'changed_by'. */ - config: string; -} - -export interface LogEntry { + disabled_host: boolean; /** - * The ID of the job this log entry is associated with. + * If the host has a dhcp group, this will include 3 fields, 'id', 'name', and 'description'. */ - job: number; + dhcp_group: string[]; /** - * The ID of the device this log entry is associated with. + * The associated attributes of the host. */ - device: number; + attributes: string[]; /** - * The timestamp of the log entry, in ISO format. + * The master IP address of the host. */ - stamp: string; + master_ip_address: string; /** - * The severity of the log entry. + * The groups that own this host */ - severity: number; + group_owners: string[]; /** - * The message of the log entry. + * The user who last changed the host. */ - message: string; -} - -export interface RecentReport { - hosts_today: number; - hosts_week: number; - hosts_month: number; - users_today: number; - users_week: number; - users_month: number; -} - -export interface Host { - mac: string; - details: string; - vendor: string; - hostname: string; - expires: string; - description: string; - is_dynamic: boolean; - disabled_host: boolean; - dhcp_group: string[]; - attributes: string[]; - master_ip_address: string; - group_owners: string[]; changed_by: User; } diff --git a/src/types/apiFilters.ts b/src/types/apiFilters.ts index 9640e60..62badd0 100644 --- a/src/types/apiFilters.ts +++ b/src/types/apiFilters.ts @@ -1,7 +1,3 @@ -// Filter interfaces for API list endpoints - -import { AdvancedFilterParams } from './api'; - export enum PythonBoolString { true = 'True', false = 'False', @@ -17,77 +13,6 @@ export function serializeBoolean(value: boolean): PythonBoolString { return value ? PythonBoolString.true : PythonBoolString.false; } -export type JobFilter = Partial<{ - id: string; - scheduled_time__gte: string; - scheduled_time__lte: string; - command: string; - device: number; -}>; - -export type JobStatusFilter = Partial<{ - job: number; - success: boolean; -}>; - -export type FirmwareFilter = Partial<{ - id: number; - filename: string; - name: string; -}>; - -export type DiscoverGroupFilter = Partial<{ - id: number; - ipamgroup: string; - username: string; -}>; - -export enum DeviceOrdering { - ID = 'id', - Host = 'host', - Model = 'model', - Version = 'version', - IP = 'ip', - Vendor = 'vendor', - Location = 'location', - PrimaryFlash = 'primary_flash', - LastUpdateSuccess = 'lastupdatesuccess', - LastUpdateError = 'lastupdateerror', - LastUpdate = 'lastupdate', - DeviceConfig = 'device_config', - Serial = 'serial', - SSH = 'ssh', - SecondaryFlash = 'secondary_flash', -} - -export enum DeviceVendor { - Aruba = 'Aruba', - Brocade = 'Brocade/Foundry', - HPE = 'HPE/Aruba', - Juniper = 'Juniper Networks', - Ubiquiti = 'Ubiquiti airOS', - Unknown = 'unknown (failed discovery)', -} - -export type DeviceFilter = AdvancedFilterParams< - Partial<{ - id: string; - host: string; - model: string; - version: string; - ip: string; - vendor: DeviceVendor; - location: string; - primary_flash: string; - lastupdatesuccess: boolean; - lastupdateerror: string; - lastupdate__gte: string; - lastupdate__lte: string; - device_config: string; - serial: string; - }> ->; - export type LogFilter = Partial<{ device: number; severity: number; @@ -97,43 +22,4 @@ export type LogFilter = Partial<{ stamp__lte: string; }>; -export type RecentReportFilter = Partial<{ - hosts_today: number; - hosts_week: number; - hosts_month: number; - users_today: number; - users_week: number; - users_month: number; -}> -export type InterfaceFilter = Partial<{ - /** - * The ID of the device to filter interfaces by. - */ - device: number; - /** - * The SNMP ID of the interface. - */ - snmpid: string; - /** - * The name of the interface. - */ - name: string; - /** - * The description of the interface. - */ - description: string; - /** - * The link speed of the interface. - */ - speed: number; -}>; - -export enum InterfaceOrdering { - SNMPID = 'snmpid', - Name = 'name', - Description = 'description', - Speed = 'speed', - IsEnabled = 'is_enabled', - IsOperational = 'is_operational', -} diff --git a/src/utilities/getCookie.ts b/src/utilities/getCookie.ts index da6b250..865c014 100644 --- a/src/utilities/getCookie.ts +++ b/src/utilities/getCookie.ts @@ -12,9 +12,3 @@ export const getCookie = (name: string): string | void => { if (cookie) return decodeURIComponent(cookie[1]); else return 'asdf'; }; - -export const setCookie = (name: string, value: string, days = 1) => { - const date = new Date(); - date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000); - document.cookie = `${name}=${value};expires=${date.toUTCString()};path=/`; -} \ No newline at end of file From 7255bede576cd94edba6c2d3326fc77dacb4a921 Mon Sep 17 00:00:00 2001 From: Treyson Date: Thu, 15 Aug 2024 15:38:28 -0600 Subject: [PATCH 54/96] fix sending of OPTIONS requests on every. single. API call. --- src/components/DomainGrid.tsx | 4 +--- src/utilities/apiFunctions.ts | 36 +++++++++++++++++------------------ 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/src/components/DomainGrid.tsx b/src/components/DomainGrid.tsx index 75e3ec1..21927c3 100644 --- a/src/components/DomainGrid.tsx +++ b/src/components/DomainGrid.tsx @@ -38,9 +38,7 @@ const DomainGrid = (): JSX.Element => { {data?.results?.length === 0 ? ( - - No domains found - + No domains found ) : ( Your Domains )} diff --git a/src/utilities/apiFunctions.ts b/src/utilities/apiFunctions.ts index eb969ae..2d1aeaf 100644 --- a/src/utilities/apiFunctions.ts +++ b/src/utilities/apiFunctions.ts @@ -38,13 +38,13 @@ export const getApiEndpointFunctions = < HttpMethod.POST, { username: string; password: string }, API.GenericResponse | StrictTypeChecking - >(HttpMethod.POST, 'login/', { headers: { 'Content-Type': 'application/json', 'X-CSRFToken': getCookie('csrftoken') ?? '' } }), + >(HttpMethod.POST, 'login/', { headers: { 'X-CSRFToken': getCookie('csrftoken') ?? '' } }), /** * Logout the current user */ logout: requestGenerator< HttpMethod.POST - >(HttpMethod.POST, 'logout/', { headers: { 'Content-Type': 'application/json', 'X-CSRFToken': getCookie('csrftoken') ?? '' } }), + >(HttpMethod.POST, 'logout/', { headers: { 'X-CSRFToken': getCookie('csrftoken') ?? '' } }), /** * Get the CSRF token for initial login @@ -54,7 +54,7 @@ export const getApiEndpointFunctions = < void, { csrfToken: string; sessionID: string } | StrictTypeChecking - >(HttpMethod.GET, 'get_csrf/', { headers: { 'Content-Type': 'application/json', } }), + >(HttpMethod.GET, 'get_csrf/', {}), /** * Gets the current user for testing purposes only :D */ @@ -62,7 +62,7 @@ export const getApiEndpointFunctions = < HttpMethod.GET, void, API.GenericResponse | StrictTypeChecking - >(HttpMethod.GET, 'whoami/', { headers: { 'Content-Type': 'application/json', } }), + >(HttpMethod.GET, 'whoami/', {}), /** * Gets current users information, including permissions */ @@ -70,7 +70,7 @@ export const getApiEndpointFunctions = < HttpMethod.GET, void, API.AuthResponse | StrictTypeChecking - >(HttpMethod.GET, 'users/me/', { headers: { 'Content-Type': 'application/json', } }), + >(HttpMethod.GET, 'users/me/', {}), }, /** * Logs API @@ -83,7 +83,7 @@ export const getApiEndpointFunctions = < HttpMethod.GET, API.PaginationParams, API.PaginatedData | StrictTypeChecking - >(HttpMethod.GET, 'logs/', { headers: { 'Content-Type': 'application/json', } }), + >(HttpMethod.GET, 'logs/', {}), /** * Gets LogEntry objects from the current user\ * Sortable: true @@ -92,7 +92,7 @@ export const getApiEndpointFunctions = < HttpMethod.GET, API.PaginationParams, API.PaginatedData | StrictTypeChecking - >(HttpMethod.GET, 'logs/my-logs/', { headers: { 'Content-Type': 'application/json', } }), + >(HttpMethod.GET, 'logs/my-logs/', {}), }, /** * Reports API @@ -105,7 +105,7 @@ export const getApiEndpointFunctions = < HttpMethod.GET, Array, API.RecentReport | StrictTypeChecking - >(HttpMethod.GET, 'report/recent-stats', { headers: { 'Content-Type': 'application/json' } }), + >(HttpMethod.GET, 'report/recent-stats', {}), }, /** * Groups API @@ -119,7 +119,7 @@ export const getApiEndpointFunctions = < HttpMethod.GET, API.PaginationParams, API.PaginatedData | StrictTypeChecking - >(HttpMethod.GET, 'groups/', { headers: { 'Content-Type': 'application/json' } }), + >(HttpMethod.GET, 'groups/', {}), }, /** * Hosts API @@ -129,7 +129,7 @@ export const getApiEndpointFunctions = < HttpMethod.GET, API.PaginationParams, API.PaginatedData | StrictTypeChecking - >(HttpMethod.GET, 'hosts/mine/', { headers: { 'Content-Type': 'application/json' } }), + >(HttpMethod.GET, 'hosts/mine/', {}), }, /** * DNS API @@ -143,7 +143,7 @@ export const getApiEndpointFunctions = < HttpMethod.GET, API.PaginationParams, API.PaginatedData | StrictTypeChecking - >(HttpMethod.GET, 'dns/', { headers: { 'Content-Type': 'application/json' } }), + >(HttpMethod.GET, 'dns/', {}), /** * Useless endpoint, just wanted to show that permissions can be checked, see api. */ @@ -151,7 +151,7 @@ export const getApiEndpointFunctions = < HttpMethod.GET, API.PaginationParams, API.PaginatedData | StrictTypeChecking - >(HttpMethod.GET, 'dns/mine', { headers: { 'Content-Type': 'application/json' } }), + >(HttpMethod.GET, 'dns/mine', {}), /** * API endpoints for a specific DNS Object * @param id @@ -164,7 +164,7 @@ export const getApiEndpointFunctions = < delete: requestGenerator( HttpMethod.DELETE, `dns/${id}/`, - { headers: { 'Content-Type': 'application/json', 'X-CSRFToken': getCookie('csrftoken') ?? '' } } + { headers: { 'X-CSRFToken': getCookie('csrftoken') ?? '' } } ), /** * Updates a DNSRecord object given an objects ID and new data @@ -173,7 +173,7 @@ export const getApiEndpointFunctions = < HttpMethod.PUT, Partial, API.DNSRecord | StrictTypeChecking - >(HttpMethod.PUT, `dns/${id}/`, { headers: { 'Content-Type': 'application/json', 'X-CSRFToken': getCookie('csrftoken') ?? '' } }), + >(HttpMethod.PUT, `dns/${id}/`, { headers: { 'X-CSRFToken': getCookie('csrftoken') ?? '' } }), }) }, /** @@ -188,7 +188,7 @@ export const getApiEndpointFunctions = < HttpMethod.GET, API.PaginationParams, API.PaginatedData | StrictTypeChecking - >(HttpMethod.GET, 'domains/', { headers: { 'Content-Type': 'application/json' } }), + >(HttpMethod.GET, 'domains/', {}), /** * API endpoints for a specific Domain Object * @param id @@ -205,7 +205,7 @@ export const getApiEndpointFunctions = < >( HttpMethod.GET, `domains/${id}/records/`, - { headers: { 'Content-Type': 'application/json' } } + {} ), }) }, @@ -221,7 +221,7 @@ export const getApiEndpointFunctions = < HttpMethod.GET, API.PaginationParams, API.PaginatedData | StrictTypeChecking - >(HttpMethod.GET, 'hosts/', { headers: { 'Content-Type': 'application/json' } }), + >(HttpMethod.GET, 'hosts/', {}), /** * API endpoints for a specific Host Object * @param mac @@ -235,7 +235,7 @@ export const getApiEndpointFunctions = < HttpMethod.GET, API.GenericResponse, API.Host | StrictTypeChecking - >(HttpMethod.GET, `hosts/${mac}/`, { headers: { 'Content-Type': 'application/json' } }), + >(HttpMethod.GET, `hosts/${mac}/`, {}), }) } }); From 0eec738fefbf41454098d644d7abfdfbea403e43 Mon Sep 17 00:00:00 2001 From: Treyson Date: Fri, 16 Aug 2024 13:18:15 -0600 Subject: [PATCH 55/96] clean and setup for hosts integration --- src/components/DomainGrid.tsx | 4 ++-- src/components/LoginForm.tsx | 3 ++- src/components/dashboard/Welcome.tsx | 22 ++++++++++++++-------- src/components/tables/PaginatedTable.tsx | 2 +- src/contexts/AuthContext.tsx | 1 + 5 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/components/DomainGrid.tsx b/src/components/DomainGrid.tsx index 21927c3..cd4352b 100644 --- a/src/components/DomainGrid.tsx +++ b/src/components/DomainGrid.tsx @@ -63,7 +63,7 @@ const DomainGrid = (): JSX.Element => { {domain.name} - + {domain.record_count} @@ -74,7 +74,7 @@ const DomainGrid = (): JSX.Element => { { ...loginResponse, is_ipamadmin: userDetailsResponse.is_ipamadmin, groups: userDetailsResponse.groups, + first_name: userDetailsResponse.first_name }); navigate('/'); @@ -71,7 +72,7 @@ const LoginForm = () => { required size="lg" /> - {error && {error}} + {error && {error}} + + Welcome to openIPAM{user?.first_name ? `, ${user.first_name}` : ''} + + + + We ask for your patience as we migrate over to our new upgraded design and implementation of openIPAM. + + + Please reach out to the IPAM team if you have any questions or concerns. + + + ); }; diff --git a/src/components/tables/PaginatedTable.tsx b/src/components/tables/PaginatedTable.tsx index e887ad4..528e1ea 100644 --- a/src/components/tables/PaginatedTable.tsx +++ b/src/components/tables/PaginatedTable.tsx @@ -44,7 +44,7 @@ interface BasePaginatedTableProps { */ title: string; /** - * + * The message to display when there is no data. */ noDataMessage?: string; /** diff --git a/src/contexts/AuthContext.tsx b/src/contexts/AuthContext.tsx index 259a9cc..b0a588a 100644 --- a/src/contexts/AuthContext.tsx +++ b/src/contexts/AuthContext.tsx @@ -12,6 +12,7 @@ interface User { id: number; is_ipamadmin: boolean; groups: string[] + first_name: string; } interface AuthContextType { From 056e7dfd2b878467db8b774a39eded9c7a0df7f4 Mon Sep 17 00:00:00 2001 From: Treyson Date: Fri, 16 Aug 2024 13:18:32 -0600 Subject: [PATCH 56/96] implement basic barebones version of hosts home page --- src/pages/Hosts.tsx | 40 +++++++++++++++++++++++++++++++++++ src/routes.tsx | 3 +++ src/utilities/apiFunctions.ts | 5 +++++ 3 files changed, 48 insertions(+) create mode 100644 src/pages/Hosts.tsx diff --git a/src/pages/Hosts.tsx b/src/pages/Hosts.tsx new file mode 100644 index 0000000..32f6653 --- /dev/null +++ b/src/pages/Hosts.tsx @@ -0,0 +1,40 @@ +import React from 'react'; +import { Button } from '@mantine/core'; +import { getApiEndpointFunctions } from '../utilities/apiFunctions'; +import PaginatedTable from '../components/tables/PaginatedTable'; + + +const Hosts: React.FC<{ viewType: "userHosts" | "allHosts" }> = ({ viewType }) => { + const api = getApiEndpointFunctions(); + const attributes = ["hostname", "mac", "expires", "master_ip_address", "vendor", "last_seen", "last_seen_ip"]; + + return ( + <> + + + {viewType === "allHosts" ? ( + + ) : ( + + )} + + ); +} + +export default Hosts; diff --git a/src/routes.tsx b/src/routes.tsx index 0204a69..3da5d19 100644 --- a/src/routes.tsx +++ b/src/routes.tsx @@ -8,6 +8,7 @@ const DomainDetail = React.lazy(() => import('./pages/DomainDetail')); const ProtectedRoute = React.lazy(() => import('./components/routes/UserProtectedRoute')); const AdminProtectedRoute = React.lazy(() => import('./components/routes/AdminProtectedRoute')); const AdminTest = React.lazy(() => import('./pages/TestAdmin')); +const Host = React.lazy(() => import('./pages/Hosts')); const routes: RouteObject[] = [ { path: '/login', element: }, @@ -21,6 +22,8 @@ const routes: RouteObject[] = [ path: '/domains/:domain', element: }, + { path: '/hosts', element: }, + { path: '/hosts/all', element: }, { path: '/admin', element: , diff --git a/src/utilities/apiFunctions.ts b/src/utilities/apiFunctions.ts index 2d1aeaf..b542c75 100644 --- a/src/utilities/apiFunctions.ts +++ b/src/utilities/apiFunctions.ts @@ -130,6 +130,11 @@ export const getApiEndpointFunctions = < API.PaginationParams, API.PaginatedData | StrictTypeChecking >(HttpMethod.GET, 'hosts/mine/', {}), + all: requestGenerator< + HttpMethod.GET, + API.PaginationParams, + API.PaginatedData | StrictTypeChecking + >(HttpMethod.GET, 'hosts/', {}), }, /** * DNS API From 5b14707034aaef5875f1f3c4892ec279e5446d08 Mon Sep 17 00:00:00 2001 From: Treyson Date: Fri, 16 Aug 2024 13:32:45 -0600 Subject: [PATCH 57/96] technically opensource, so made organization name customizable via env --- README.md | 2 +- example.env | 1 + index.html | 1 - src/config.ts | 3 +++ src/main.tsx | 4 ++++ src/pages/Hosts.tsx | 5 +++-- 6 files changed, 12 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 839b6d4..754373c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # OpenIPAM Frontend -This is a WIP rework of the openIPAM front end at USU. openIPAM is currently setup through Django admin dashboard, this is bringing it to a React based app to remove its connection with the Django REST framework. +This is a WIP rework of the openIPAM front end. openIPAM is currently setup through Django admin dashboard, this is bringing it to a React based app to remove its connection with the Django REST framework. ## Setup diff --git a/example.env b/example.env index 0a736a2..dd4c836 100644 --- a/example.env +++ b/example.env @@ -1,5 +1,6 @@ VITE_API_URL=http://127.0.0.1:8000/api/v2 VITE_FRONTEND_URL=http://localhost:5173 +VITE_ORGANIZATION_NAME=USU # Allows for use of local host and 127.0.0.1 in development VITE_DEVELOPMENT=true \ No newline at end of file diff --git a/index.html b/index.html index 4d8c03e..b92f578 100644 --- a/index.html +++ b/index.html @@ -4,7 +4,6 @@ - openIPAM | USU
diff --git a/src/config.ts b/src/config.ts index 2b9273b..e27d7ce 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,6 +1,7 @@ export interface AppConfig { apiUrl: HostName; frontendUrl: HostName; + organizationName: string; // Add more configuration options here } @@ -26,11 +27,13 @@ const validateHostName = (url: string): HostName => { const apiUrl = validateHostName(import.meta.env.VITE_API_URL as string); const frontendUrl = validateHostName(import.meta.env.VITE_FRONTEND_URL as string); +const organizationName = import.meta.env.VITE_ORGANIZATION_NAME as string; // Add more configuration options here const config: AppConfig = { apiUrl, frontendUrl, + organizationName, }; export default config; diff --git a/src/main.tsx b/src/main.tsx index e63eef4..9dcb57a 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -2,6 +2,10 @@ import React from 'react' import ReactDOM from 'react-dom/client' import App from './App.tsx' +const organizationName = import.meta.env.VITE_ORGANIZATION_NAME as string +document.title = `openIPAM | ${organizationName}`; + + ReactDOM.createRoot(document.getElementById('root')!).render( diff --git a/src/pages/Hosts.tsx b/src/pages/Hosts.tsx index 32f6653..f982e6d 100644 --- a/src/pages/Hosts.tsx +++ b/src/pages/Hosts.tsx @@ -2,11 +2,12 @@ import React from 'react'; import { Button } from '@mantine/core'; import { getApiEndpointFunctions } from '../utilities/apiFunctions'; import PaginatedTable from '../components/tables/PaginatedTable'; - +import { useConfig } from '../contexts/ConfigContext'; const Hosts: React.FC<{ viewType: "userHosts" | "allHosts" }> = ({ viewType }) => { const api = getApiEndpointFunctions(); const attributes = ["hostname", "mac", "expires", "master_ip_address", "vendor", "last_seen", "last_seen_ip"]; + const { config } = useConfig(); return ( <> @@ -20,7 +21,7 @@ const Hosts: React.FC<{ viewType: "userHosts" | "allHosts" }> = ({ viewType }) = >Your hosts {viewType === "allHosts" ? ( Date: Fri, 16 Aug 2024 13:50:43 -0600 Subject: [PATCH 58/96] change paginated table to handle date objects --- src/components/tables/PaginatedTable.tsx | 30 ++++++++++++++---------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/src/components/tables/PaginatedTable.tsx b/src/components/tables/PaginatedTable.tsx index 528e1ea..65069e7 100644 --- a/src/components/tables/PaginatedTable.tsx +++ b/src/components/tables/PaginatedTable.tsx @@ -373,18 +373,24 @@ const PaginatedTable = (props: PaginatedTableProps): JSX.Element => { /> )} - {neededAttr.map(attr => ( - - {isEditing && editableFields?.includes(attr) ? ( - handleEditInputChange(attr, e.currentTarget.value)} - /> - ) : ( - item[attr] - )} - - ))} + {neededAttr.map(attr => { + const value = item[attr]; + const isDate = !isNaN(Date.parse(value)); + + return ( + + {isEditing && editableFields?.includes(attr) ? ( + handleEditInputChange(attr, e.currentTarget.value)} + /> + ) : ( + isDate ? new Date(value).toLocaleString() : value + )} + + ); + })} + {editableObj && editFunction && ( From 88c32998051c720bd076c354df808947d2ccb9c0 Mon Sep 17 00:00:00 2001 From: Treyson Date: Fri, 16 Aug 2024 14:36:14 -0600 Subject: [PATCH 59/96] use regex for date finder isntead, as previous 'fix' broke all TTLs --- src/components/tables/PaginatedTable.tsx | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/components/tables/PaginatedTable.tsx b/src/components/tables/PaginatedTable.tsx index 65069e7..8a4393a 100644 --- a/src/components/tables/PaginatedTable.tsx +++ b/src/components/tables/PaginatedTable.tsx @@ -375,7 +375,15 @@ const PaginatedTable = (props: PaginatedTableProps): JSX.Element => { )} {neededAttr.map(attr => { const value = item[attr]; - const isDate = !isNaN(Date.parse(value)); + const dateRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,6})?([+-]\d{2}:\d{2}|Z)?$/; + const isDate = dateRegex.test(value); + + const formatDate = (dateStr: string) => { + const date = new Date(dateStr); + const optionsDate: Intl.DateTimeFormatOptions = { day: 'numeric', month: 'long', year: 'numeric' }; + const optionsTime: Intl.DateTimeFormatOptions = { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: true }; + return `${date.toLocaleDateString('en-US', optionsDate)} ${date.toLocaleTimeString('en-US', optionsTime)}`; + }; return ( @@ -385,12 +393,11 @@ const PaginatedTable = (props: PaginatedTableProps): JSX.Element => { onChange={(e) => handleEditInputChange(attr, e.currentTarget.value)} /> ) : ( - isDate ? new Date(value).toLocaleString() : value + isDate ? formatDate(value) : value )} ); })} - {editableObj && editFunction && ( From a734d87cee9f5ac804da603f9c109424c2879027 Mon Sep 17 00:00:00 2001 From: Treyson Date: Fri, 16 Aug 2024 14:36:59 -0600 Subject: [PATCH 60/96] Fix home screen hosts table, clean --- src/components/dashboard/UserHosts.tsx | 2 +- src/pages/Hosts.tsx | 2 ++ src/utilities/apiFunctions.ts | 44 ++++++++++++-------------- 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/components/dashboard/UserHosts.tsx b/src/components/dashboard/UserHosts.tsx index 311a125..dff44af 100644 --- a/src/components/dashboard/UserHosts.tsx +++ b/src/components/dashboard/UserHosts.tsx @@ -9,7 +9,7 @@ const UserHosts = () => { getFunction={api.hosts.myhosts} title='Your Hosts' noDataMessage='No hosts found' - neededAttr={['hostname', 'ip_address', 'mac_address', 'description']} + neededAttr={['hostname', 'mac', 'vendor', 'description']} defPageSize={10} /> ); diff --git a/src/pages/Hosts.tsx b/src/pages/Hosts.tsx index f982e6d..077aef5 100644 --- a/src/pages/Hosts.tsx +++ b/src/pages/Hosts.tsx @@ -25,6 +25,7 @@ const Hosts: React.FC<{ viewType: "userHosts" | "allHosts" }> = ({ viewType }) = getFunction={api.hosts.all} neededAttr={attributes} defPageSize={10} + noDataMessage={"No hosts found"} /> ) : ( = ({ viewType }) = neededAttr={attributes} getFunction={api.hosts.myhosts} defPageSize={10} + noDataMessage={"No owned hosts found. If you feel this is an error, please contact an administrator."} /> )} diff --git a/src/utilities/apiFunctions.ts b/src/utilities/apiFunctions.ts index b542c75..182e4b7 100644 --- a/src/utilities/apiFunctions.ts +++ b/src/utilities/apiFunctions.ts @@ -45,7 +45,6 @@ export const getApiEndpointFunctions = < logout: requestGenerator< HttpMethod.POST >(HttpMethod.POST, 'logout/', { headers: { 'X-CSRFToken': getCookie('csrftoken') ?? '' } }), - /** * Get the CSRF token for initial login */ @@ -53,8 +52,7 @@ export const getApiEndpointFunctions = < HttpMethod.GET, void, { csrfToken: string; sessionID: string } | StrictTypeChecking - - >(HttpMethod.GET, 'get_csrf/', {}), + >(HttpMethod.GET, 'get_csrf/'), /** * Gets the current user for testing purposes only :D */ @@ -62,7 +60,7 @@ export const getApiEndpointFunctions = < HttpMethod.GET, void, API.GenericResponse | StrictTypeChecking - >(HttpMethod.GET, 'whoami/', {}), + >(HttpMethod.GET, 'whoami/'), /** * Gets current users information, including permissions */ @@ -70,7 +68,7 @@ export const getApiEndpointFunctions = < HttpMethod.GET, void, API.AuthResponse | StrictTypeChecking - >(HttpMethod.GET, 'users/me/', {}), + >(HttpMethod.GET, 'users/me/'), }, /** * Logs API @@ -83,7 +81,7 @@ export const getApiEndpointFunctions = < HttpMethod.GET, API.PaginationParams, API.PaginatedData | StrictTypeChecking - >(HttpMethod.GET, 'logs/', {}), + >(HttpMethod.GET, 'logs/'), /** * Gets LogEntry objects from the current user\ * Sortable: true @@ -92,7 +90,7 @@ export const getApiEndpointFunctions = < HttpMethod.GET, API.PaginationParams, API.PaginatedData | StrictTypeChecking - >(HttpMethod.GET, 'logs/my-logs/', {}), + >(HttpMethod.GET, 'logs/my-logs/'), }, /** * Reports API @@ -105,7 +103,7 @@ export const getApiEndpointFunctions = < HttpMethod.GET, Array, API.RecentReport | StrictTypeChecking - >(HttpMethod.GET, 'report/recent-stats', {}), + >(HttpMethod.GET, 'report/recent-stats'), }, /** * Groups API @@ -119,22 +117,28 @@ export const getApiEndpointFunctions = < HttpMethod.GET, API.PaginationParams, API.PaginatedData | StrictTypeChecking - >(HttpMethod.GET, 'groups/', {}), + >(HttpMethod.GET, 'groups/'), }, /** * Hosts API */ hosts: { + /** + * Gets Host objects owned by the current user + */ myhosts: requestGenerator< HttpMethod.GET, API.PaginationParams, API.PaginatedData | StrictTypeChecking - >(HttpMethod.GET, 'hosts/mine/', {}), + >(HttpMethod.GET, 'hosts/mine/'), + /** + * Gets All Host objects + */ all: requestGenerator< HttpMethod.GET, API.PaginationParams, API.PaginatedData | StrictTypeChecking - >(HttpMethod.GET, 'hosts/', {}), + >(HttpMethod.GET, 'hosts/'), }, /** * DNS API @@ -148,7 +152,7 @@ export const getApiEndpointFunctions = < HttpMethod.GET, API.PaginationParams, API.PaginatedData | StrictTypeChecking - >(HttpMethod.GET, 'dns/', {}), + >(HttpMethod.GET, 'dns/'), /** * Useless endpoint, just wanted to show that permissions can be checked, see api. */ @@ -156,7 +160,7 @@ export const getApiEndpointFunctions = < HttpMethod.GET, API.PaginationParams, API.PaginatedData | StrictTypeChecking - >(HttpMethod.GET, 'dns/mine', {}), + >(HttpMethod.GET, 'dns/mine'), /** * API endpoints for a specific DNS Object * @param id @@ -193,7 +197,7 @@ export const getApiEndpointFunctions = < HttpMethod.GET, API.PaginationParams, API.PaginatedData | StrictTypeChecking - >(HttpMethod.GET, 'domains/', {}), + >(HttpMethod.GET, 'domains/'), /** * API endpoints for a specific Domain Object * @param id @@ -207,11 +211,7 @@ export const getApiEndpointFunctions = < HttpMethod.GET, API.PaginationParams, API.PaginatedData | StrictTypeChecking - >( - HttpMethod.GET, - `domains/${id}/records/`, - {} - ), + >(HttpMethod.GET, `domains/${id}/records/`), }) }, /** @@ -226,7 +226,7 @@ export const getApiEndpointFunctions = < HttpMethod.GET, API.PaginationParams, API.PaginatedData | StrictTypeChecking - >(HttpMethod.GET, 'hosts/', {}), + >(HttpMethod.GET, 'hosts/'), /** * API endpoints for a specific Host Object * @param mac @@ -240,7 +240,7 @@ export const getApiEndpointFunctions = < HttpMethod.GET, API.GenericResponse, API.Host | StrictTypeChecking - >(HttpMethod.GET, `hosts/${mac}/`, {}), + >(HttpMethod.GET, `hosts/${mac}/`), }) } }); @@ -482,8 +482,6 @@ function requestGenerator< headers: { ...headers, ...extraHeaders, - // Add the CSRF token to the headers, overriding any value that - // might have been passed in the extra headers }, signal: controller?.signal, body: form ? (data as FormData) : JSON.stringify(data), From 94438bf255b88eb10e0cbb666e0abda6c5c5ada7 Mon Sep 17 00:00:00 2001 From: Treyson Date: Fri, 16 Aug 2024 15:22:31 -0600 Subject: [PATCH 61/96] Better compartmentalized the host control --- src/components/HostControl.tsx | 29 ++++++++++++++++ src/components/tables/PaginatedTable.tsx | 16 ++++----- src/pages/Hosts.tsx | 43 ------------------------ src/pages/{ => domains}/DomainDetail.tsx | 4 +-- src/pages/{ => domains}/Domains.tsx | 2 +- src/pages/hosts/AllHosts.tsx | 22 ++++++++++++ src/pages/hosts/UserHosts.tsx | 22 ++++++++++++ src/routes.tsx | 11 +++--- 8 files changed, 90 insertions(+), 59 deletions(-) create mode 100644 src/components/HostControl.tsx delete mode 100644 src/pages/Hosts.tsx rename src/pages/{ => domains}/DomainDetail.tsx (87%) rename src/pages/{ => domains}/Domains.tsx (63%) create mode 100644 src/pages/hosts/AllHosts.tsx create mode 100644 src/pages/hosts/UserHosts.tsx diff --git a/src/components/HostControl.tsx b/src/components/HostControl.tsx new file mode 100644 index 0000000..4f57cc6 --- /dev/null +++ b/src/components/HostControl.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import { SegmentedControl } from '@mantine/core'; +import { useNavigate } from 'react-router-dom'; +import { useConfig } from '../contexts/ConfigContext'; + +const HostControl: React.FC<{ currentSelection: string }> = ({ currentSelection }) => { + const navigate = useNavigate(); + const { config } = useConfig(); + return ( + { + if (value === "allHosts") { + navigate("/hosts/all"); + } else if (value === "userHosts") { + navigate("/hosts"); + } + }} + color='blue' + size='lg' + /> + ) +} + +export default HostControl; \ No newline at end of file diff --git a/src/components/tables/PaginatedTable.tsx b/src/components/tables/PaginatedTable.tsx index 8a4393a..ca9f2bb 100644 --- a/src/components/tables/PaginatedTable.tsx +++ b/src/components/tables/PaginatedTable.tsx @@ -242,6 +242,13 @@ const PaginatedTable = (props: PaginatedTableProps): JSX.Element => { setOrderBy(key); }; + const handleFormatDate = (dateStr: string) => { + const date = new Date(dateStr); + const optionsDate: Intl.DateTimeFormatOptions = { day: 'numeric', month: 'long', year: 'numeric' }; + const optionsTime: Intl.DateTimeFormatOptions = { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: true }; + return `${date.toLocaleDateString('en-US', optionsDate)} ${date.toLocaleTimeString('en-US', optionsTime)}`; + }; + const handleFormatHeader = (header: string) => header.replace(/[_-]/g, ' ').replace(/\b\w/g, (char: string) => char.toUpperCase()); const handleCheckboxChange = (item: any, checked: boolean) => { @@ -378,13 +385,6 @@ const PaginatedTable = (props: PaginatedTableProps): JSX.Element => { const dateRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,6})?([+-]\d{2}:\d{2}|Z)?$/; const isDate = dateRegex.test(value); - const formatDate = (dateStr: string) => { - const date = new Date(dateStr); - const optionsDate: Intl.DateTimeFormatOptions = { day: 'numeric', month: 'long', year: 'numeric' }; - const optionsTime: Intl.DateTimeFormatOptions = { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: true }; - return `${date.toLocaleDateString('en-US', optionsDate)} ${date.toLocaleTimeString('en-US', optionsTime)}`; - }; - return ( {isEditing && editableFields?.includes(attr) ? ( @@ -393,7 +393,7 @@ const PaginatedTable = (props: PaginatedTableProps): JSX.Element => { onChange={(e) => handleEditInputChange(attr, e.currentTarget.value)} /> ) : ( - isDate ? formatDate(value) : value + isDate ? handleFormatDate(value) : value )} ); diff --git a/src/pages/Hosts.tsx b/src/pages/Hosts.tsx deleted file mode 100644 index 077aef5..0000000 --- a/src/pages/Hosts.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import React from 'react'; -import { Button } from '@mantine/core'; -import { getApiEndpointFunctions } from '../utilities/apiFunctions'; -import PaginatedTable from '../components/tables/PaginatedTable'; -import { useConfig } from '../contexts/ConfigContext'; - -const Hosts: React.FC<{ viewType: "userHosts" | "allHosts" }> = ({ viewType }) => { - const api = getApiEndpointFunctions(); - const attributes = ["hostname", "mac", "expires", "master_ip_address", "vendor", "last_seen", "last_seen_ip"]; - const { config } = useConfig(); - - return ( - <> - - - {viewType === "allHosts" ? ( - - ) : ( - - )} - - ); -} - -export default Hosts; diff --git a/src/pages/DomainDetail.tsx b/src/pages/domains/DomainDetail.tsx similarity index 87% rename from src/pages/DomainDetail.tsx rename to src/pages/domains/DomainDetail.tsx index 2035c16..b407de7 100644 --- a/src/pages/DomainDetail.tsx +++ b/src/pages/domains/DomainDetail.tsx @@ -1,6 +1,6 @@ import { useParams } from 'react-router-dom'; -import PaginatedTable from '../components/tables/PaginatedTable'; -import { getApiEndpointFunctions } from '../utilities/apiFunctions'; +import PaginatedTable from '../../components/tables/PaginatedTable'; +import { getApiEndpointFunctions } from '../../utilities/apiFunctions'; const DomainDetail = () => { const { domain } = useParams<{ domain: string }>() as { domain: string | number }; diff --git a/src/pages/Domains.tsx b/src/pages/domains/Domains.tsx similarity index 63% rename from src/pages/Domains.tsx rename to src/pages/domains/Domains.tsx index aeab338..082115b 100644 --- a/src/pages/Domains.tsx +++ b/src/pages/domains/Domains.tsx @@ -1,4 +1,4 @@ -import DomainGrid from '../components/DomainGrid'; +import DomainGrid from '../../components/DomainGrid'; const Domains = () => { return ( diff --git a/src/pages/hosts/AllHosts.tsx b/src/pages/hosts/AllHosts.tsx new file mode 100644 index 0000000..b913fc3 --- /dev/null +++ b/src/pages/hosts/AllHosts.tsx @@ -0,0 +1,22 @@ +import { getApiEndpointFunctions } from '../../utilities/apiFunctions'; +import PaginatedTable from '../../components/tables/PaginatedTable'; +import HostControl from '../../components/HostControl'; + +const AllHosts = () => { + const api = getApiEndpointFunctions(); + + return ( + <> + + + + ); +} + +export default AllHosts; diff --git a/src/pages/hosts/UserHosts.tsx b/src/pages/hosts/UserHosts.tsx new file mode 100644 index 0000000..c9d7d91 --- /dev/null +++ b/src/pages/hosts/UserHosts.tsx @@ -0,0 +1,22 @@ +import HostControl from '../../components/HostControl'; +import { getApiEndpointFunctions } from '../../utilities/apiFunctions'; +import PaginatedTable from '../../components/tables/PaginatedTable'; + +const UserHosts = () => { + const api = getApiEndpointFunctions(); + + return ( + <> + + + + ); +} + +export default UserHosts; diff --git a/src/routes.tsx b/src/routes.tsx index 3da5d19..b49d482 100644 --- a/src/routes.tsx +++ b/src/routes.tsx @@ -3,12 +3,13 @@ import { RouteObject } from 'react-router-dom'; const Home = React.lazy(() => import('./pages/Home')); const Login = React.lazy(() => import('./pages/Login')); -const Dns = React.lazy(() => import('./pages/Domains')); -const DomainDetail = React.lazy(() => import('./pages/DomainDetail')); +const Dns = React.lazy(() => import('./pages/domains/Domains')); +const DomainDetail = React.lazy(() => import('./pages/domains/DomainDetail')); const ProtectedRoute = React.lazy(() => import('./components/routes/UserProtectedRoute')); const AdminProtectedRoute = React.lazy(() => import('./components/routes/AdminProtectedRoute')); const AdminTest = React.lazy(() => import('./pages/TestAdmin')); -const Host = React.lazy(() => import('./pages/Hosts')); +const UserHosts = React.lazy(() => import('./pages/hosts/UserHosts')); +const AllHosts = React.lazy(() => import('./pages/hosts/AllHosts')); const routes: RouteObject[] = [ { path: '/login', element: }, @@ -22,8 +23,8 @@ const routes: RouteObject[] = [ path: '/domains/:domain', element: }, - { path: '/hosts', element: }, - { path: '/hosts/all', element: }, + { path: '/hosts', element: }, + { path: '/hosts/all', element: }, { path: '/admin', element: , From b654bc770cae2e8dedefc4d781677cc2134b29d4 Mon Sep 17 00:00:00 2001 From: Treyson Date: Fri, 16 Aug 2024 15:41:19 -0600 Subject: [PATCH 62/96] alter navbar to highlight headers when page is child of the header, see /hosts/ and /hosts/all/ --- src/components/Navbar.tsx | 99 ++++++++++++++++++++------------------- 1 file changed, 51 insertions(+), 48 deletions(-) diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index c2872d9..21f749c 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -1,4 +1,3 @@ -import { useState, useEffect } from 'react'; import { Container, Group, @@ -126,56 +125,60 @@ export function Navbar() { const { isAdmin } = useAuth(); const currentPath = location.pathname; - const [active, setActive] = useState(currentPath); - - useEffect(() => { - setActive(currentPath); - }, [currentPath]); const finalLinks = isAdmin() ? adminLinks : links; - const items = finalLinks.map((link) => ( - - - { - if (link.link) { - setActive(link.link); - close(); - } - }} - > - {link.label} - - - {link.dropdown ? ( - - {link.dropdown.map((item) => ( - item.isLabel ? ( -
{item.label}
- ) : ( - { - if (item.link) { - setActive(item.link); - close(); - } - }} - > - {item.label} - - ) - ))} -
- ) : null} -
- )); + const items = finalLinks.map((link) => { + const isActive = link.link + ? currentPath === link.link || (currentPath.startsWith(link.link) && link.link !== '/') + : false; + + return ( + + + { + if (link.link) { + close(); + } + }} + > + {link.label} + + + {link.dropdown ? ( + + {link.dropdown.map((item) => { + const itemActive = item.link + ? currentPath === item.link || (currentPath.startsWith(item.link) && item.link !== '/') + : false; + + return item.isLabel ? ( +
{item.label}
+ ) : ( + { + if (item.link) { + close(); + } + }} + > + {item.label} + + ); + })} +
+ ) : null} +
+ ); + }); return (
From 745597027e3edb581758caba3756a763915c4339 Mon Sep 17 00:00:00 2001 From: Treyson Date: Fri, 16 Aug 2024 16:32:03 -0600 Subject: [PATCH 63/96] further setup navbar and admin links --- src/components/Navbar.tsx | 6 +++--- src/components/dashboard/index.tsx | 16 ---------------- src/components/tables/PaginatedTable.tsx | 2 +- src/routes.tsx | 17 ++++++++++++++++- 4 files changed, 20 insertions(+), 21 deletions(-) diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index 21f749c..4f4c72f 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -62,7 +62,7 @@ const adminLinks: DropdownLink[] = [ ] }, { - link: '', + link: '/network', label: 'Network', dropdown: [ { label: 'Addresses', link: '/11' }, @@ -83,7 +83,7 @@ const adminLinks: DropdownLink[] = [ ] }, { - link: '', + link: '/admin', label: 'Admin', dropdown: [ { isLabel: true, label: 'Users & Groups' }, @@ -105,7 +105,7 @@ const adminLinks: DropdownLink[] = [ ] }, { - link: '', + link: '/reports', label: 'Reports', dropdown: [ { label: 'OpenIPAM Stats', link: '/39' }, diff --git a/src/components/dashboard/index.tsx b/src/components/dashboard/index.tsx index f6cc4df..29d0191 100644 --- a/src/components/dashboard/index.tsx +++ b/src/components/dashboard/index.tsx @@ -1,26 +1,10 @@ -import { useEffect } from 'react'; import { Grid } from '@mantine/core'; -import { useNavigate } from 'react-router-dom'; -import { useAuth } from '../../contexts/AuthContext'; import Actions from './Actions'; import Stats from './Stats'; import Welcome from './Welcome'; import UserHosts from './UserHosts'; const DashBoard = () => { - const { user } = useAuth(); - const navigate = useNavigate(); - - useEffect(() => { - if (!user) { - navigate('/login'); - } - }, [user, navigate]); - - if (!user) { - return
Loading...
; - } - return ( diff --git a/src/components/tables/PaginatedTable.tsx b/src/components/tables/PaginatedTable.tsx index ca9f2bb..11a949d 100644 --- a/src/components/tables/PaginatedTable.tsx +++ b/src/components/tables/PaginatedTable.tsx @@ -245,7 +245,7 @@ const PaginatedTable = (props: PaginatedTableProps): JSX.Element => { const handleFormatDate = (dateStr: string) => { const date = new Date(dateStr); const optionsDate: Intl.DateTimeFormatOptions = { day: 'numeric', month: 'long', year: 'numeric' }; - const optionsTime: Intl.DateTimeFormatOptions = { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: true }; + const optionsTime: Intl.DateTimeFormatOptions = { hour: '2-digit', minute: '2-digit', hour12: true }; return `${date.toLocaleDateString('en-US', optionsDate)} ${date.toLocaleTimeString('en-US', optionsTime)}`; }; diff --git a/src/routes.tsx b/src/routes.tsx index b49d482..0d3fd86 100644 --- a/src/routes.tsx +++ b/src/routes.tsx @@ -25,11 +25,26 @@ const routes: RouteObject[] = [ }, { path: '/hosts', element: }, { path: '/hosts/all', element: }, + // Admin only routes. + { + path: '/network', + element: , + children: [ + { path: '/network', element: } + ] + }, { path: '/admin', element: , children: [ - { path: 'asdf', element: } + { path: '/admin', element: } + ] + }, + { + path: '/reports', + element: , + children: [ + { path: '/reports', element: } ] } ] From 908db06367503b1ccfb281d3115a9139a9620ccd Mon Sep 17 00:00:00 2001 From: Treyson Date: Mon, 19 Aug 2024 10:44:53 -0600 Subject: [PATCH 64/96] Make whole card clickable --- src/components/DomainGrid.tsx | 41 ++++++++++++++++------------------- 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/src/components/DomainGrid.tsx b/src/components/DomainGrid.tsx index cd4352b..078cfe3 100644 --- a/src/components/DomainGrid.tsx +++ b/src/components/DomainGrid.tsx @@ -53,25 +53,23 @@ const DomainGrid = (): JSX.Element => { {data?.results?.map((domain: any, index: number) => ( - - - + + + {domain.name} - - - {domain.record_count} - - - - {domain.description} - - - + + {domain.record_count} + + + + {domain.description} + + { > - - - + + + ))} - ); }; From 4bb4860fbbc2d771b7ac437f0ce1e058d638358d Mon Sep 17 00:00:00 2001 From: Treyson Date: Mon, 19 Aug 2024 10:45:44 -0600 Subject: [PATCH 65/96] Allow for expiration dates to be highlighted based on status --- src/components/tables/PaginatedTable.tsx | 28 +++++++++++++++++++++--- src/pages/hosts/AllHosts.tsx | 7 +++--- src/pages/hosts/UserHosts.tsx | 3 ++- 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/src/components/tables/PaginatedTable.tsx b/src/components/tables/PaginatedTable.tsx index 11a949d..731501f 100644 --- a/src/components/tables/PaginatedTable.tsx +++ b/src/components/tables/PaginatedTable.tsx @@ -17,7 +17,8 @@ import { TextInput, ActionIcon, Tooltip, - Flex + Flex, + Loader, } from '@mantine/core'; import { FaRegCircleXmark, @@ -47,6 +48,12 @@ interface BasePaginatedTableProps { * The message to display when there is no data. */ noDataMessage?: string; + /** + * Whether to highlight the dates in the table. + * Defaults to false. + * Green if the date is in the future, red if the date is in the past. (useful for expiration) + */ + highlightDates?: boolean; /** * The attributes from the API to display in the table. */ @@ -176,6 +183,7 @@ const PaginatedTable = (props: PaginatedTableProps): JSX.Element => { neededAttr, morePageSizes, overridePageSizes, + highlightDates, editableObj, sortable, sortableFields, @@ -319,7 +327,10 @@ const PaginatedTable = (props: PaginatedTableProps): JSX.Element => { return ( - {title} + + {title} + {loading && } + {maxPages !== 1 && } @@ -384,6 +395,7 @@ const PaginatedTable = (props: PaginatedTableProps): JSX.Element => { const value = item[attr]; const dateRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,6})?([+-]\d{2}:\d{2}|Z)?$/; const isDate = dateRegex.test(value); + const pastOrFuture = new Date(value) < new Date(); return ( @@ -393,7 +405,17 @@ const PaginatedTable = (props: PaginatedTableProps): JSX.Element => { onChange={(e) => handleEditInputChange(attr, e.currentTarget.value)} /> ) : ( - isDate ? handleFormatDate(value) : value + isDate ? ( + highlightDates ? ( + + {handleFormatDate(value)} + + ) : ( + handleFormatDate(value) + ) + ) : ( + value + ) )} ); diff --git a/src/pages/hosts/AllHosts.tsx b/src/pages/hosts/AllHosts.tsx index b913fc3..d7dc5ba 100644 --- a/src/pages/hosts/AllHosts.tsx +++ b/src/pages/hosts/AllHosts.tsx @@ -1,19 +1,20 @@ import { getApiEndpointFunctions } from '../../utilities/apiFunctions'; import PaginatedTable from '../../components/tables/PaginatedTable'; import HostControl from '../../components/HostControl'; - +import { useConfig } from '../../contexts/ConfigContext'; const AllHosts = () => { const api = getApiEndpointFunctions(); - + const { config } = useConfig(); return ( <> ); diff --git a/src/pages/hosts/UserHosts.tsx b/src/pages/hosts/UserHosts.tsx index c9d7d91..f8347a2 100644 --- a/src/pages/hosts/UserHosts.tsx +++ b/src/pages/hosts/UserHosts.tsx @@ -13,7 +13,8 @@ const UserHosts = () => { neededAttr={["hostname", "mac", "expires", "master_ip_address", "vendor", "last_seen", "last_seen_ip"]} getFunction={api.hosts.myhosts} defPageSize={10} - noDataMessage={"No owned hosts found. If you feel this is an error, please contact an administrator."} + noDataMessage={"You don't own any hosts. If you feel this is an error, please contact an administrator."} + highlightDates={true} /> ); From 33d0ee495860b09293e4ea12d5a56adb57651bee Mon Sep 17 00:00:00 2001 From: Treyson Date: Mon, 19 Aug 2024 11:34:53 -0600 Subject: [PATCH 66/96] setup eslint prettier and a precommit hook to call prettier --- .eslintrc.cjs | 21 +- .prettierrc | 7 + package-lock.json | 274 +++++++++++--- package.json | 15 +- src/App.tsx | 53 ++- src/components/DomainGrid.tsx | 33 +- src/components/HostControl.tsx | 29 +- src/components/LoginForm.tsx | 39 +- src/components/Navbar.tsx | 87 +++-- src/components/dashboard/Actions.tsx | 4 +- src/components/dashboard/Stats.tsx | 77 +++- src/components/dashboard/UserHosts.tsx | 8 +- src/components/dashboard/Welcome.tsx | 28 +- src/components/routes/AdminProtectedRoute.tsx | 4 +- src/components/routes/UserProtectedRoute.tsx | 4 +- src/components/tables/PaginatedTable.tsx | 353 +++++++++++++----- src/config.ts | 10 +- src/contexts/AuthContext.tsx | 12 +- src/contexts/ConfigContext.tsx | 17 +- src/hooks/useApi.ts | 97 ++--- src/hooks/useCsrfToken.ts | 11 +- src/main.tsx | 17 +- src/pages/Home.tsx | 3 +- src/pages/Login.tsx | 3 +- src/pages/TestAdmin.tsx | 2 +- src/pages/domains/DomainDetail.tsx | 8 +- src/pages/domains/Domains.tsx | 6 +- src/pages/hosts/AllHosts.tsx | 16 +- src/pages/hosts/UserHosts.tsx | 20 +- src/routes.tsx | 29 +- src/types/api.ts | 28 +- src/types/apiFilters.ts | 2 - src/types/index.ts | 14 +- src/utilities/apiFunctions.ts | 63 ++-- src/utilities/dateUtils.ts | 7 +- src/utilities/typeGuards.ts | 15 +- vite.config.ts | 5 +- yarn.lock | 209 ++++++++--- 38 files changed, 1098 insertions(+), 532 deletions(-) create mode 100644 .prettierrc diff --git a/.eslintrc.cjs b/.eslintrc.cjs index bf8f1cd..dcf3493 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -1,18 +1,19 @@ module.exports = { - root: true, - env: { browser: true, es2020: true }, + parser: '@typescript-eslint/parser', extends: [ 'eslint:recommended', 'plugin:@typescript-eslint/recommended', - 'plugin:react-hooks/recommended', + 'plugin:prettier/recommended', ], - ignorePatterns: ['dist', '.eslintrc.cjs'], - parser: '@typescript-eslint/parser', - plugins: ['react-refresh'], + plugins: ['@typescript-eslint'], rules: { - 'react-refresh/only-export-components': [ - 'warn', - { allowConstantExport: true }, - ], + 'quotes': ['error', 'single'], + 'semi': ['error', 'always'], + 'prettier/prettier': 'error', + }, + env: { + browser: true, + node: true, + es6: true, }, }; diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..3d2fd27 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,7 @@ +{ + "singleQuote": true, + "semi": true, + "trailingComma": "all", + "printWidth": 80, + "tabWidth": 4 +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index a47ae76..b3d92a6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,17 +36,21 @@ "@types/object-hash": "^3.0.6", "@types/react": "^18.2.66", "@types/react-dom": "^18.2.22", - "@typescript-eslint/eslint-plugin": "^7.2.0", - "@typescript-eslint/parser": "^7.2.0", + "@typescript-eslint/eslint-plugin": "^7.18.0", + "@typescript-eslint/parser": "^7.18.0", "@vitejs/plugin-react-swc": "^3.5.0", "dotenv": "^16.4.5", "eslint": "^8.57.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-prettier": "^5.2.1", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.6", + "eslint-plugin-vue": "^9.27.0", "jest": "^29.7.0", "postcss": "^8.4.38", "postcss-preset-mantine": "^1.15.0", "postcss-simple-vars": "^7.0.1", + "prettier": "^3.3.3", "typescript": "^5.2.2", "vite": "^5.2.0" } @@ -1933,6 +1937,18 @@ "node": ">= 8" } }, + "node_modules/@pkgr/core": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", + "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, "node_modules/@remix-run/router": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.17.1.tgz", @@ -2688,16 +2704,16 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.12.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.12.0.tgz", - "integrity": "sha512-7F91fcbuDf/d3S8o21+r3ZncGIke/+eWk0EpO21LXhDfLahriZF9CGj4fbAetEjlaBdjdSm9a6VeXbpbT6Z40Q==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz", + "integrity": "sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "7.12.0", - "@typescript-eslint/type-utils": "7.12.0", - "@typescript-eslint/utils": "7.12.0", - "@typescript-eslint/visitor-keys": "7.12.0", + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/type-utils": "7.18.0", + "@typescript-eslint/utils": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -2721,15 +2737,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "7.12.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.12.0.tgz", - "integrity": "sha512-dm/J2UDY3oV3TKius2OUZIFHsomQmpHtsV0FTh1WO8EKgHLQ1QCADUqscPgTpU+ih1e21FQSRjXckHn3txn6kQ==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.18.0.tgz", + "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "7.12.0", - "@typescript-eslint/types": "7.12.0", - "@typescript-eslint/typescript-estree": "7.12.0", - "@typescript-eslint/visitor-keys": "7.12.0", + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", "debug": "^4.3.4" }, "engines": { @@ -2749,13 +2765,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "7.12.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.12.0.tgz", - "integrity": "sha512-itF1pTnN6F3unPak+kutH9raIkL3lhH1YRPGgt7QQOh43DQKVJXmWkpb+vpc/TiDHs6RSd9CTbDsc/Y+Ygq7kg==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz", + "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.12.0", - "@typescript-eslint/visitor-keys": "7.12.0" + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -2766,13 +2782,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "7.12.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.12.0.tgz", - "integrity": "sha512-lib96tyRtMhLxwauDWUp/uW3FMhLA6D0rJ8T7HmH7x23Gk1Gwwu8UZ94NMXBvOELn6flSPiBrCKlehkiXyaqwA==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.18.0.tgz", + "integrity": "sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "7.12.0", - "@typescript-eslint/utils": "7.12.0", + "@typescript-eslint/typescript-estree": "7.18.0", + "@typescript-eslint/utils": "7.18.0", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, @@ -2793,9 +2809,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "7.12.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.12.0.tgz", - "integrity": "sha512-o+0Te6eWp2ppKY3mLCU+YA9pVJxhUJE15FV7kxuD9jgwIAa+w/ycGJBMrYDTpVGUM/tgpa9SeMOugSabWFq7bg==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz", + "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==", "dev": true, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -2806,13 +2822,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.12.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.12.0.tgz", - "integrity": "sha512-5bwqLsWBULv1h6pn7cMW5dXX/Y2amRqLaKqsASVwbBHMZSnHqE/HN4vT4fE0aFsiwxYvr98kqOWh1a8ZKXalCQ==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz", + "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.12.0", - "@typescript-eslint/visitor-keys": "7.12.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -2834,15 +2850,15 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "7.12.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.12.0.tgz", - "integrity": "sha512-Y6hhwxwDx41HNpjuYswYp6gDbkiZ8Hin9Bf5aJQn1bpTs3afYY4GX+MPYxma8jtoIV2GRwTM/UJm/2uGCVv+DQ==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz", + "integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "7.12.0", - "@typescript-eslint/types": "7.12.0", - "@typescript-eslint/typescript-estree": "7.12.0" + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -2856,12 +2872,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.12.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.12.0.tgz", - "integrity": "sha512-uZk7DevrQLL3vSnfFl5bj4sL75qC9D6EdjemIdbtkuUmIheWpuiiylSY01JxJE7+zGrOWDZrp1WxOuDntvKrHQ==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz", + "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.12.0", + "@typescript-eslint/types": "7.18.0", "eslint-visitor-keys": "^3.4.3" }, "engines": { @@ -3152,6 +3168,12 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true + }, "node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", @@ -3848,6 +3870,48 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint-config-prettier": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", + "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.1.tgz", + "integrity": "sha512-gH3iR3g4JfF+yYPaJYkN7jEl9QbweL/YfkoRlNnuIEHEz1vHVlCmWOS+eGGiRuzHQXdJFCOTxRgvju9b8VUmrw==", + "dev": true, + "dependencies": { + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.9.1" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": "*", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } + } + }, "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", @@ -3869,6 +3933,28 @@ "eslint": ">=7" } }, + "node_modules/eslint-plugin-vue": { + "version": "9.27.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.27.0.tgz", + "integrity": "sha512-5Dw3yxEyuBSXTzT5/Ge1X5kIkRTQ3nvBn/VwPwInNiZBSJOO/timWMUaflONnFBzU6NhB68lxnCda7ULV5N7LA==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "globals": "^13.24.0", + "natural-compare": "^1.4.0", + "nth-check": "^2.1.1", + "postcss-selector-parser": "^6.0.15", + "semver": "^7.6.0", + "vue-eslint-parser": "^9.4.3", + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": "^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0" + } + }, "node_modules/eslint-scope": { "version": "7.2.2", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", @@ -4049,6 +4135,12 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true + }, "node_modules/fast-equals": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.0.1.tgz", @@ -5779,9 +5871,9 @@ } }, "node_modules/minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" @@ -5855,6 +5947,18 @@ "node": ">=8" } }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -6248,6 +6352,33 @@ "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, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/pretty-format": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", @@ -6988,6 +7119,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/synckit": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.1.tgz", + "integrity": "sha512-7gr8p9TQP6RAHusBOSLs46F4564ZrjV8xFmw5zCmgmhGUcw2hxsShhJ6CEiHQMgPDwAQ1fWHPM0ypc4RMAig4A==", + "dev": true, + "dependencies": { + "@pkgr/core": "^0.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, "node_modules/tabbable": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", @@ -7353,6 +7500,30 @@ } } }, + "node_modules/vue-eslint-parser": { + "version": "9.4.3", + "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.3.tgz", + "integrity": "sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==", + "dev": true, + "dependencies": { + "debug": "^4.3.4", + "eslint-scope": "^7.1.1", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.3.1", + "esquery": "^1.4.0", + "lodash": "^4.17.21", + "semver": "^7.3.6" + }, + "engines": { + "node": "^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=6.0.0" + } + }, "node_modules/walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", @@ -7422,6 +7593,15 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, + "node_modules/xml-name-validator": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/package.json b/package.json index 527a9ac..5f29350 100644 --- a/package.json +++ b/package.json @@ -6,8 +6,9 @@ "scripts": { "dev": "vite", "build": "tsc && vite build", - "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", - "preview": "vite preview" + "lint": "eslint . --ext .ts,.tsx", + "preview": "vite preview", + "format": "prettier --write \"src/**/*.{ts,tsx}\"" }, "dependencies": { "@mantine/charts": "^7.10.1", @@ -38,18 +39,22 @@ "@types/object-hash": "^3.0.6", "@types/react": "^18.2.66", "@types/react-dom": "^18.2.22", - "@typescript-eslint/eslint-plugin": "^7.2.0", - "@typescript-eslint/parser": "^7.2.0", + "@typescript-eslint/eslint-plugin": "^7.18.0", + "@typescript-eslint/parser": "^7.18.0", "@vitejs/plugin-react-swc": "^3.5.0", "dotenv": "^16.4.5", "eslint": "^8.57.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-prettier": "^5.2.1", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.6", + "eslint-plugin-vue": "^9.27.0", "jest": "^29.7.0", "postcss": "^8.4.38", "postcss-preset-mantine": "^1.15.0", "postcss-simple-vars": "^7.0.1", + "prettier": "^3.3.3", "typescript": "^5.2.2", "vite": "^5.2.0" } -} +} \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx index 6df8dfe..aedf2d6 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,10 +1,6 @@ import React from 'react'; import '@mantine/core/styles.css'; -import { - MantineProvider, - Container, - ColorSchemeScript -} from '@mantine/core'; +import { MantineProvider, Container, ColorSchemeScript } from '@mantine/core'; import { BrowserRouter, useRoutes } from 'react-router-dom'; import config from './config'; import { ConfigProvider } from './contexts/ConfigContext'; @@ -13,32 +9,33 @@ import Layout from './components/Layout'; import routes from './routes'; function AppRoutes() { - const element = useRoutes(routes); - return <>{element}; + const element = useRoutes(routes); + return <>{element}; } - function App() { - return ( - <> - - - - - - - - Loading...
}> - - - - - - - - - - ); + return ( + <> + + + + + + + + Loading...
} + > + + + + + + + + + + ); } export default App; diff --git a/src/components/DomainGrid.tsx b/src/components/DomainGrid.tsx index 078cfe3..65ffdb9 100644 --- a/src/components/DomainGrid.tsx +++ b/src/components/DomainGrid.tsx @@ -1,7 +1,7 @@ import { useState, useEffect } from 'react'; import { usePaginatedApi } from '../hooks/useApi'; import { getApiEndpointFunctions } from '../utilities/apiFunctions'; -import { FaEye } from "react-icons/fa"; +import { FaEye } from 'react-icons/fa'; import { Link } from 'react-router-dom'; import { Card, @@ -15,18 +15,13 @@ import { Pagination, } from '@mantine/core'; - const DomainGrid = (): JSX.Element => { const PAGE_SIZE = 12; const [page, setPage] = useState(1); const [maxPages, setMaxPages] = useState(0); const api = getApiEndpointFunctions(); - const { data } = usePaginatedApi( - api.domain.get, - page, - PAGE_SIZE - ); + const { data } = usePaginatedApi(api.domain.get, page, PAGE_SIZE); useEffect(() => { if (data?.count) { @@ -35,8 +30,8 @@ const DomainGrid = (): JSX.Element => { }, [data]); return ( - - + + {data?.results?.length === 0 ? ( No domains found ) : ( @@ -52,8 +47,14 @@ const DomainGrid = (): JSX.Element => { {data?.results?.map((domain: any, index: number) => ( - - + + { padding="lg" > - {domain.name} + + {domain.name} + {domain.record_count} @@ -69,7 +72,11 @@ const DomainGrid = (): JSX.Element => { {domain.description} - + = ({ currentSelection }) => { +const HostControl: React.FC<{ currentSelection: string }> = ({ + currentSelection, +}) => { const navigate = useNavigate(); const { config } = useConfig(); return ( { - if (value === "allHosts") { - navigate("/hosts/all"); - } else if (value === "userHosts") { - navigate("/hosts"); + if (value === 'allHosts') { + navigate('/hosts/all'); + } else if (value === 'userHosts') { + navigate('/hosts'); } }} - color='blue' - size='lg' + color="blue" + size="lg" /> - ) -} + ); +}; -export default HostControl; \ No newline at end of file +export default HostControl; diff --git a/src/components/LoginForm.tsx b/src/components/LoginForm.tsx index 0b5d818..c0552b3 100644 --- a/src/components/LoginForm.tsx +++ b/src/components/LoginForm.tsx @@ -5,7 +5,7 @@ import { Group, Container, Paper, - Title + Title, } from '@mantine/core'; import { useState } from 'react'; import { useNavigate } from 'react-router-dom'; @@ -37,29 +37,44 @@ const LoginForm = () => { ...loginResponse, is_ipamadmin: userDetailsResponse.is_ipamadmin, groups: userDetailsResponse.groups, - first_name: userDetailsResponse.first_name + first_name: userDetailsResponse.first_name, }); navigate('/'); } else { setError('Login failed: Invalid user data'); } - } catch (error: any) { - setError('Login failed. Please check your credentials and try again.'); + } catch (error: unknown) { + if (error instanceof Error) { + setError( + 'Login failed. Please check your credentials and try again.', + ); + } else { + setError('An unexpected error occurred.'); + } } }; return ( - Login -
{ e.preventDefault(); handleLogin(); }}> + + Login + + { + e.preventDefault(); + handleLogin(); + }} + > setUsername(event.currentTarget.value)} + onChange={(event) => + setUsername(event.currentTarget.value) + } required size="lg" /> @@ -68,11 +83,17 @@ const LoginForm = () => { label="Password" placeholder="Your password" value={password} - onChange={(event) => setPassword(event.currentTarget.value)} + onChange={(event) => + setPassword(event.currentTarget.value) + } required size="lg" /> - {error && {error}} + {error && ( + + {error} + + )} + ); }; -export default Welcome; \ No newline at end of file +export default Welcome; diff --git a/src/components/routes/AdminProtectedRoute.tsx b/src/components/routes/AdminProtectedRoute.tsx index f929a97..28ec486 100644 --- a/src/components/routes/AdminProtectedRoute.tsx +++ b/src/components/routes/AdminProtectedRoute.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { Navigate, Outlet } from 'react-router-dom'; import { useAuth } from '../../contexts/AuthContext'; /** - * For use with react-router-dom, this component will redirect the user to the + * For use with react-router-dom, this component will redirect the user to the * home page if they are not an admin. * @returns Redirect or Outlet */ @@ -10,7 +10,7 @@ const AdminRoute: React.FC = () => { const { user, isAdmin } = useAuth(); if (!user || !isAdmin()) { - return ; + return ; } return ; }; diff --git a/src/components/routes/UserProtectedRoute.tsx b/src/components/routes/UserProtectedRoute.tsx index 7737ac4..c69808d 100644 --- a/src/components/routes/UserProtectedRoute.tsx +++ b/src/components/routes/UserProtectedRoute.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { Navigate, Outlet } from 'react-router-dom'; import { useAuth } from '../../contexts/AuthContext'; /** - * For use with react-router-dom, this component will redirect the user to the + * For use with react-router-dom, this component will redirect the user to the * home page if they are not logged in. * @returns Redirect or Outlet */ @@ -10,7 +10,7 @@ const ProtectedRoute: React.FC = () => { const { user } = useAuth(); if (!user) { - return ; + return ; } return ; }; diff --git a/src/components/tables/PaginatedTable.tsx b/src/components/tables/PaginatedTable.tsx index 731501f..c895745 100644 --- a/src/components/tables/PaginatedTable.tsx +++ b/src/components/tables/PaginatedTable.tsx @@ -28,7 +28,7 @@ import { FaSortDown, FaCheck, FaXmark, - FaPencil + FaPencil, } from 'react-icons/fa6'; interface BasePaginatedTableProps { @@ -49,7 +49,7 @@ interface BasePaginatedTableProps { */ noDataMessage?: string; /** - * Whether to highlight the dates in the table. + * Whether to highlight the dates in the table. * Defaults to false. * Green if the date is in the future, red if the date is in the past. (useful for expiration) */ @@ -70,9 +70,9 @@ interface BasePaginatedTableProps { /** * Whether the objects in the table are sortable. * Sortability depends on API support. In the Django API, you must alter the - * View or viewset. In the filter_queryset function, we will get queryparams; + * View or viewset. In the filter_queryset function, we will get queryparams; * `order_by` and `direction` and sort the queryset accordingly. - * + * * For a quick example, see openipam/api_v2/views/logs.py, lines 47-55. */ sortable?: boolean; @@ -88,30 +88,30 @@ interface BasePaginatedTableProps { * Searchability depends on API support. In the Django API, you must alter the * View. To do so, you must obtain the query parameters for the searchable fields, and * filter the queryset accordingly. - * + * * For a quick example, see openipam/api_v2/views/users, lines 60-72. - */ + */ searchable?: boolean; /** * The fields that are searchable. * Requires `searchable` to be set. * All searchable fields need to be set up in the API. They won't just work. - */ + */ searchableFields?: string[]; /** * The additional URL parameters to pass to the API. * Pass it in like *additionalUrlParams={{ "paramName": String(value)) }}* - * + * * There are no current use cases for this. But it can be used to pass in additional * parameters to the get function of the API. - */ + */ additionalUrlParams?: Record; } interface EditablePaginatedTableProps extends BasePaginatedTableProps { /** * Whether the objects in the table are editable. - * + * * DO not set this to true if you dont have at least one of the following: * - 'actions' and 'actionFunctions' * - 'editFunction' and 'editableFields' @@ -130,12 +130,15 @@ interface EditablePaginatedTableProps extends BasePaginatedTableProps { * OR * functionName: { func: previouslyDefinedFunction, key: 'id' } */ - actionFunctions?: Record void, key: string }>; + actionFunctions?: Record< + string, + { func: (params: any) => void; key: string } + >; /** * The PUT function defined in /utilities/apiFunctions.ts used to edit the data. * The way you pass the function is a bit different. See the example in DomainDetail.tsx. * Requires `editableObj` to be true. - */ + */ editFunction?: (dnsName: string) => QueryRequest; /** * The editable fields @@ -150,32 +153,36 @@ interface NonEditablePaginatedTableProps extends BasePaginatedTableProps { editableObj?: false; } -type PaginatedTableProps = EditablePaginatedTableProps | NonEditablePaginatedTableProps; +type PaginatedTableProps = + | EditablePaginatedTableProps + | NonEditablePaginatedTableProps; /** - * The PaginatedTable component is a flexible and reusable component + * The PaginatedTable component is a flexible and reusable component * that displays paginated data in a table format. It supports sorting, actions, editing, * searching, page size selection, and more. - * + * * To properly use most features in this component, you must pass in the necessary props. - * For example, if you want to use sorting, you cannot just set sortable to true. You + * For example, if you want to use sorting, you cannot just set sortable to true. You * must also state what fields are sortable in the sortableFields prop. This is because * the API may not support sorting on all fields. - * + * * If you want to use searching, you must set searchable to true and pass in the fields * that are searchable in the searchableFields prop. See the props documentation for more. - * + * * If you want to use actions, you must pass in the actions and actionFunctions props. * For more information, see the props documentation above. - * + * * If you want to use editing, you must pass in the editFunction and editableFields props. * For more information, see the props documentation above. */ const PaginatedTable = (props: PaginatedTableProps): JSX.Element => { const actions = (props as EditablePaginatedTableProps).actions ?? []; - const actionFunctions = (props as EditablePaginatedTableProps).actionFunctions ?? {}; + const actionFunctions = + (props as EditablePaginatedTableProps).actionFunctions ?? {}; const editFunction = (props as EditablePaginatedTableProps).editFunction; - const editableFields = (props as EditablePaginatedTableProps).editableFields; + const editableFields = (props as EditablePaginatedTableProps) + .editableFields; const { getFunction, defPageSize, @@ -189,10 +196,12 @@ const PaginatedTable = (props: PaginatedTableProps): JSX.Element => { sortableFields, searchable, searchableFields, - additionalUrlParams + additionalUrlParams, } = props; const [data, setData] = useState([]); - const [noDataMessage] = useState(props.noDataMessage || "No data found"); + const [noDataMessage] = useState( + props.noDataMessage || 'No data found', + ); const [maxPages, setMaxPages] = useState(0); const [pageSize, setPageSize] = useState(defPageSize || 5); const [page, setPage] = useState(1); @@ -210,19 +219,27 @@ const PaginatedTable = (props: PaginatedTableProps): JSX.Element => { page, pageSize, { - ...orderBy ? { 'order_by': orderBy, 'direction': direction } : {}, - ...Object.fromEntries(Object.entries(searchTerms).filter(([_, v]) => v)), - ...additionalUrlParams - } + ...(orderBy ? { order_by: orderBy, direction: direction } : {}), + ...Object.fromEntries( + Object.entries(searchTerms).filter(([_, v]) => v), + ), + ...additionalUrlParams, + }, ); const pageSizes = ['5', '10', '20']; - if (overridePageSizes) { pageSizes.length = 0; } + if (overridePageSizes) { + pageSizes.length = 0; + } if (morePageSizes) { const uniqueSizes = new Set([...pageSizes, ...morePageSizes]); pageSizes.length = 0; - pageSizes.push(...Array.from(uniqueSizes).sort((a, b) => parseInt(a) - parseInt(b))); + pageSizes.push( + ...Array.from(uniqueSizes).sort( + (a, b) => parseInt(a) - parseInt(b), + ), + ); } const handlePageSizeChange = (value: string | null) => { @@ -231,7 +248,7 @@ const PaginatedTable = (props: PaginatedTableProps): JSX.Element => { }; const handleSearchChange = (field: string, value: string) => { - setSearchTerms(prevTerms => ({ ...prevTerms, [field]: value })); + setSearchTerms((prevTerms) => ({ ...prevTerms, [field]: value })); }; const handleActionChange = (value: string | null) => { @@ -252,15 +269,26 @@ const PaginatedTable = (props: PaginatedTableProps): JSX.Element => { const handleFormatDate = (dateStr: string) => { const date = new Date(dateStr); - const optionsDate: Intl.DateTimeFormatOptions = { day: 'numeric', month: 'long', year: 'numeric' }; - const optionsTime: Intl.DateTimeFormatOptions = { hour: '2-digit', minute: '2-digit', hour12: true }; + const optionsDate: Intl.DateTimeFormatOptions = { + day: 'numeric', + month: 'long', + year: 'numeric', + }; + const optionsTime: Intl.DateTimeFormatOptions = { + hour: '2-digit', + minute: '2-digit', + hour12: true, + }; return `${date.toLocaleDateString('en-US', optionsDate)} ${date.toLocaleTimeString('en-US', optionsTime)}`; }; - const handleFormatHeader = (header: string) => header.replace(/[_-]/g, ' ').replace(/\b\w/g, (char: string) => char.toUpperCase()); + const handleFormatHeader = (header: string) => + header + .replace(/[_-]/g, ' ') + .replace(/\b\w/g, (char: string) => char.toUpperCase()); const handleCheckboxChange = (item: any, checked: boolean) => { - setSelectedObjs(prevSelectedObjs => { + setSelectedObjs((prevSelectedObjs) => { const updatedSelectedObjs = new Set(prevSelectedObjs); if (checked) { updatedSelectedObjs.add(item); @@ -294,14 +322,19 @@ const PaginatedTable = (props: PaginatedTableProps): JSX.Element => { const handleEditClick = (item: any) => { setEditingRow(item.id); - setEditValues(editableFields?.reduce((acc, field) => { - acc[field] = item[field] || ''; - return acc; - }, {} as Record) || {}); + setEditValues( + editableFields?.reduce( + (acc, field) => { + acc[field] = item[field] || ''; + return acc; + }, + {} as Record, + ) || {}, + ); }; const handleEditInputChange = (field: string, value: string) => { - setEditValues(prevValues => ({ ...prevValues, [field]: value })); + setEditValues((prevValues) => ({ ...prevValues, [field]: value })); }; const handleEditSubmit = async (item: any) => { @@ -325,56 +358,106 @@ const PaginatedTable = (props: PaginatedTableProps): JSX.Element => { }, [paginatedData, pageSize, orderBy, direction]); return ( - - - + + + {title} {loading && } - {maxPages !== 1 && - - } + {maxPages !== 1 && ( + + )}
- {editableObj && actions.length && } - {neededAttr.map(attr => ( - + {editableObj && actions.length && ( + + )} + {neededAttr.map((attr) => ( + ))} - {editableObj && actions.length !== 0 && } - {neededAttr.map(attr => ( + {editableObj && actions.length !== 0 && ( + + )} + {neededAttr.map((attr) => ( - + {handleFormatHeader(attr)} - {sortable && sortableFields?.includes(attr) && ( - handleSort(attr, direction)}> - {orderBy === attr ? (direction === 'asc' ? : ) : } - - )} - {searchable && searchableFields?.includes(attr) && ( - handleSearchChange(attr, e.currentTarget.value)} - size='xs' - /> - )} + {sortable && + sortableFields?.includes(attr) && ( + + handleSort( + attr, + direction, + ) + } + > + {orderBy === attr ? ( + direction === 'asc' ? ( + + ) : ( + + ) + ) : ( + + )} + + )} + {searchable && + searchableFields?.includes( + attr, + ) && ( + + handleSearchChange( + attr, + e.currentTarget + .value, + ) + } + size="xs" + /> + )} ))} - {editableObj && editFunction && Edit} + {editableObj && editFunction && ( + Edit + )} {data.length === 0 ? ( - - - {loading ? 'Loading...' : data.length === 0 ? noDataMessage : null} + + + {loading + ? 'Loading...' + : data.length === 0 + ? noDataMessage + : null} @@ -386,36 +469,70 @@ const PaginatedTable = (props: PaginatedTableProps): JSX.Element => { {editableObj && actions.length > 0 && ( obj.id === item.id)} - onChange={(event) => handleCheckboxChange(item, event.currentTarget.checked)} + checked={Array.from( + selectedObjs, + ).some( + (obj) => + obj.id === item.id, + )} + onChange={(event) => + handleCheckboxChange( + item, + event.currentTarget + .checked, + ) + } /> )} - {neededAttr.map(attr => { + {neededAttr.map((attr) => { const value = item[attr]; - const dateRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,6})?([+-]\d{2}:\d{2}|Z)?$/; - const isDate = dateRegex.test(value); - const pastOrFuture = new Date(value) < new Date(); + const dateRegex = + /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,6})?([+-]\d{2}:\d{2}|Z)?$/; + const isDate = + dateRegex.test(value); + const pastOrFuture = + new Date(value) < new Date(); return ( - {isEditing && editableFields?.includes(attr) ? ( + {isEditing && + editableFields?.includes( + attr, + ) ? ( handleEditInputChange(attr, e.currentTarget.value)} + value={ + editValues[attr] + } + onChange={(e) => + handleEditInputChange( + attr, + e + .currentTarget + .value, + ) + } /> - ) : ( - isDate ? ( - highlightDates ? ( - - {handleFormatDate(value)} - - ) : ( - handleFormatDate(value) - ) + ) : isDate ? ( + highlightDates ? ( + + {handleFormatDate( + value, + )} + ) : ( - value + handleFormatDate( + value, + ) ) + ) : ( + value )} ); @@ -430,7 +547,11 @@ const PaginatedTable = (props: PaginatedTableProps): JSX.Element => { size={'lg'} mr={8} color="green" - onClick={() => handleEditSubmit(item)} + onClick={() => + handleEditSubmit( + item, + ) + } > @@ -440,8 +561,12 @@ const PaginatedTable = (props: PaginatedTableProps): JSX.Element => { size={'lg'} color="red" onClick={() => { - setEditingRow(null); - setEditValues({}); + setEditingRow( + null, + ); + setEditValues( + {}, + ); }} > @@ -451,7 +576,11 @@ const PaginatedTable = (props: PaginatedTableProps): JSX.Element => { ) : ( handleEditClick(item)} + onClick={() => + handleEditClick( + item, + ) + } size={'lg'} > @@ -468,7 +597,15 @@ const PaginatedTable = (props: PaginatedTableProps): JSX.Element => {
- +
{data.length >= +pageSizes[0] && ( + + + + + + + + + + ); +}; + +export default AddRecordModal; diff --git a/src/components/DomainGrid.tsx b/src/components/domains/DomainGrid.tsx similarity index 96% rename from src/components/DomainGrid.tsx rename to src/components/domains/DomainGrid.tsx index 65ffdb9..d06c8ba 100644 --- a/src/components/DomainGrid.tsx +++ b/src/components/domains/DomainGrid.tsx @@ -1,6 +1,6 @@ import { useState, useEffect } from 'react'; -import { usePaginatedApi } from '../hooks/useApi'; -import { getApiEndpointFunctions } from '../utilities/apiFunctions'; +import { usePaginatedApi } from '../../hooks/useApi'; +import { getApiEndpointFunctions } from '../../utilities/apiFunctions'; import { FaEye } from 'react-icons/fa'; import { Link } from 'react-router-dom'; import { diff --git a/src/components/HostControl.tsx b/src/components/hosts/HostControl.tsx similarity index 94% rename from src/components/HostControl.tsx rename to src/components/hosts/HostControl.tsx index 0507adb..3fed966 100644 --- a/src/components/HostControl.tsx +++ b/src/components/hosts/HostControl.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { SegmentedControl } from '@mantine/core'; import { useNavigate } from 'react-router-dom'; -import { useConfig } from '../contexts/ConfigContext'; +import { useConfig } from '../../contexts/ConfigContext'; const HostControl: React.FC<{ currentSelection: string }> = ({ currentSelection, diff --git a/src/components/tables/PaginatedTable.tsx b/src/components/tables/PaginatedTable.tsx index c895745..280f04f 100644 --- a/src/components/tables/PaginatedTable.tsx +++ b/src/components/tables/PaginatedTable.tsx @@ -644,7 +644,7 @@ const PaginatedTable = (props: PaginatedTableProps): JSX.Element => { /> )} - {editableObj && ( + {editableObj && actions.length > 0 && (
+ + + ); }; diff --git a/src/pages/domains/Domains.tsx b/src/pages/domains/Domains.tsx index 15f95bc..a1684c4 100644 --- a/src/pages/domains/Domains.tsx +++ b/src/pages/domains/Domains.tsx @@ -1,4 +1,4 @@ -import DomainGrid from '../../components/DomainGrid'; +import DomainGrid from '../../components/domains/DomainGrid'; const Domains = () => { return ; diff --git a/src/pages/hosts/AllHosts.tsx b/src/pages/hosts/AllHosts.tsx index fb729b4..2d4cacd 100644 --- a/src/pages/hosts/AllHosts.tsx +++ b/src/pages/hosts/AllHosts.tsx @@ -1,13 +1,16 @@ import { getApiEndpointFunctions } from '../../utilities/apiFunctions'; import PaginatedTable from '../../components/tables/PaginatedTable'; -import HostControl from '../../components/HostControl'; +import HostControl from '../../components/hosts/HostControl'; import { useConfig } from '../../contexts/ConfigContext'; +import { Flex } from '@mantine/core'; const AllHosts = () => { const api = getApiEndpointFunctions(); const { config } = useConfig(); return ( <> - + + + { const api = getApiEndpointFunctions(); return ( <> - + + + import('./pages/TestAdmin')); const UserHosts = React.lazy(() => import('./pages/hosts/UserHosts')); const AllHosts = React.lazy(() => import('./pages/hosts/AllHosts')); +const PageNotFound = React.lazy(() => import('./pages/PageNotFound')); const routes: RouteObject[] = [ { path: '/login', element: }, @@ -47,6 +48,7 @@ const routes: RouteObject[] = [ }, ], }, + { path: '*', element: }, ]; export default routes; diff --git a/src/styles/index.css b/src/styles/index.css index a13ba10..e39ddb6 100644 --- a/src/styles/index.css +++ b/src/styles/index.css @@ -2,7 +2,8 @@ height: rem(56px); margin-bottom: rem(28px); background-color: var(--mantine-color-body); - border-bottom: rem(1px) solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4)); + border-bottom: rem(1px) solid + light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4)); } .inner { @@ -23,7 +24,10 @@ font-weight: 500; @mixin hover { - background-color: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-6)); + background-color: light-dark( + var(--mantine-color-gray-0), + var(--mantine-color-dark-6) + ); } [data-mantine-color-scheme] &[data-active] { @@ -35,4 +39,4 @@ .header-link { text-decoration: none; color: light-dark(var(--mantine-color-gray-7), var(--mantine-color-dark-0)); -} \ No newline at end of file +} diff --git a/src/types/api.ts b/src/types/api.ts index 68c5829..b287ad8 100644 --- a/src/types/api.ts +++ b/src/types/api.ts @@ -259,9 +259,13 @@ export interface DNSRecord { */ name: string; /** - * the content of the DNS record. (IP address, hostname, etc.) + * The IP Content of the DNS record. */ - content: string; + ip_content: string; + /** + * The text content of the DNS record. + */ + text_content: string; /** * The dns type of the DNS record. */ diff --git a/src/utilities/apiFunctions.ts b/src/utilities/apiFunctions.ts index c326fbe..c869e45 100644 --- a/src/utilities/apiFunctions.ts +++ b/src/utilities/apiFunctions.ts @@ -1,4 +1,3 @@ -// import { get } from 'http'; import { API } from '../types'; import { serializeBoolean } from '../types/apiFilters'; import { getCookie } from './getCookie'; @@ -163,6 +162,18 @@ export const getApiEndpointFunctions = < API.PaginationParams, API.PaginatedData | StrictTypeChecking >(HttpMethod.GET, 'dns/mine'), + + /** + * Creates a new DNSRecord object + */ + create: requestGenerator< + HttpMethod.POST, + Partial, + API.DNSRecord | StrictTypeChecking + >(HttpMethod.POST, 'dns/', { + headers: { 'X-CSRFToken': getCookie('csrftoken') ?? '' }, + }), + /** * API endpoints for a specific DNS Object * @param id diff --git a/yarn.lock b/yarn.lock index cc93855..423bd3c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -732,10 +732,10 @@ dependencies: clsx "^2.1.1" -"@mantine/form@^7.10.1": - version "7.10.1" - resolved "https://registry.npmjs.org/@mantine/form/-/form-7.10.1.tgz" - integrity sha512-mZwzg4GEWKEDKEIZu9FmSpGFzYYhFD2YArVOXUM0MMciUqX7yxSCon1PaPJxrV8ldc6FE+JLVI2+G2KVxJ3ZXA== +"@mantine/form@^7.12.1": + version "7.12.1" + resolved "https://registry.npmjs.org/@mantine/form/-/form-7.12.1.tgz" + integrity sha512-Q+lpgG9N8srlsI0IPnD1V1c2ZaI0xmR3bBEVm+LttSos6Q5zkG49Yy011mc0cXzEKUk2h48j8PLoPHfSEzO03g== dependencies: fast-deep-equal "^3.1.3" klona "^2.0.6" From e5e39944d7c453af689df457a9415ce5809572df Mon Sep 17 00:00:00 2001 From: Treyson Date: Wed, 21 Aug 2024 12:54:35 -0600 Subject: [PATCH 68/96] Pretty add dns record button --- src/pages/domains/DomainDetail.tsx | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/pages/domains/DomainDetail.tsx b/src/pages/domains/DomainDetail.tsx index 9de0f3a..a53a231 100644 --- a/src/pages/domains/DomainDetail.tsx +++ b/src/pages/domains/DomainDetail.tsx @@ -2,8 +2,9 @@ import { useParams } from 'react-router-dom'; import PaginatedTable from '../../components/tables/PaginatedTable'; import { getApiEndpointFunctions } from '../../utilities/apiFunctions'; import AddRecordModal from '../../components/domains/AddRecordModal'; -import { Button } from '@mantine/core'; +import { ActionIcon, Group, Card, Title } from '@mantine/core'; import { useDisclosure } from '@mantine/hooks'; +import { FaPlus } from 'react-icons/fa'; const DomainDetail = () => { const { domain } = useParams<{ domain: string }>() as { @@ -26,7 +27,16 @@ const DomainDetail = () => { return ( <> - + + + + Add DNS Record + + + + + + Date: Wed, 21 Aug 2024 13:24:36 -0600 Subject: [PATCH 69/96] Change apiFunctions to not extend a useless filter, and add search field for domains. Not functional. --- src/components/domains/DomainGrid.tsx | 22 ++++++++++++++++++++-- src/utilities/apiFunctions.ts | 20 ++++++++++---------- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/src/components/domains/DomainGrid.tsx b/src/components/domains/DomainGrid.tsx index d06c8ba..5d26394 100644 --- a/src/components/domains/DomainGrid.tsx +++ b/src/components/domains/DomainGrid.tsx @@ -1,7 +1,7 @@ import { useState, useEffect } from 'react'; import { usePaginatedApi } from '../../hooks/useApi'; import { getApiEndpointFunctions } from '../../utilities/apiFunctions'; -import { FaEye } from 'react-icons/fa'; +import { FaEye, FaSearch } from 'react-icons/fa'; import { Link } from 'react-router-dom'; import { Card, @@ -13,6 +13,7 @@ import { Group, ActionIcon, Pagination, + TextInput, } from '@mantine/core'; const DomainGrid = (): JSX.Element => { @@ -35,7 +36,24 @@ const DomainGrid = (): JSX.Element => { {data?.results?.length === 0 ? ( No domains found ) : ( - Your Domains + + Your Domains + + { + console.log('Search clicked'); + }} + > + + + )} {maxPages !== 1 && ( , + API.PaginationParams, API.PaginatedData | StrictTypeChecking >(HttpMethod.GET, 'logs/'), /** @@ -89,7 +89,7 @@ export const getApiEndpointFunctions = < */ mylogs: requestGenerator< HttpMethod.GET, - API.PaginationParams, + API.PaginationParams, API.PaginatedData | StrictTypeChecking >(HttpMethod.GET, 'logs/my-logs/'), }, @@ -116,7 +116,7 @@ export const getApiEndpointFunctions = < */ get: requestGenerator< HttpMethod.GET, - API.PaginationParams, + API.PaginationParams, API.PaginatedData | StrictTypeChecking >(HttpMethod.GET, 'groups/'), }, @@ -129,7 +129,7 @@ export const getApiEndpointFunctions = < */ myhosts: requestGenerator< HttpMethod.GET, - API.PaginationParams, + API.PaginationParams, API.PaginatedData | StrictTypeChecking >(HttpMethod.GET, 'hosts/mine/'), /** @@ -137,7 +137,7 @@ export const getApiEndpointFunctions = < */ all: requestGenerator< HttpMethod.GET, - API.PaginationParams, + API.PaginationParams, API.PaginatedData | StrictTypeChecking >(HttpMethod.GET, 'hosts/'), }, @@ -151,7 +151,7 @@ export const getApiEndpointFunctions = < */ get: requestGenerator< HttpMethod.GET, - API.PaginationParams, + API.PaginationParams, API.PaginatedData | StrictTypeChecking >(HttpMethod.GET, 'dns/'), /** @@ -159,7 +159,7 @@ export const getApiEndpointFunctions = < */ mine: requestGenerator< HttpMethod.GET, - API.PaginationParams, + API.PaginationParams, API.PaginatedData | StrictTypeChecking >(HttpMethod.GET, 'dns/mine'), @@ -212,7 +212,7 @@ export const getApiEndpointFunctions = < */ get: requestGenerator< HttpMethod.GET, - API.PaginationParams, + API.PaginationParams, API.PaginatedData | StrictTypeChecking >(HttpMethod.GET, 'domains/'), /** @@ -226,7 +226,7 @@ export const getApiEndpointFunctions = < */ getRecords: requestGenerator< HttpMethod.GET, - API.PaginationParams, + API.PaginationParams, API.PaginatedData | StrictTypeChecking >(HttpMethod.GET, `domains/${id}/records/`), }), @@ -241,7 +241,7 @@ export const getApiEndpointFunctions = < */ get: requestGenerator< HttpMethod.GET, - API.PaginationParams, + API.PaginationParams, API.PaginatedData | StrictTypeChecking >(HttpMethod.GET, 'hosts/'), /** From 2de7562de9f3938b5cad44719d1e591681aee3ca Mon Sep 17 00:00:00 2001 From: Treyson Date: Wed, 21 Aug 2024 15:30:04 -0600 Subject: [PATCH 70/96] Implement debounce, and domain search works. --- src/components/domains/DomainGrid.tsx | 54 +++++++++++++----------- src/components/tables/PaginatedTable.tsx | 4 +- src/pages/domains/DomainDetail.tsx | 2 +- 3 files changed, 34 insertions(+), 26 deletions(-) diff --git a/src/components/domains/DomainGrid.tsx b/src/components/domains/DomainGrid.tsx index 5d26394..42a7e2b 100644 --- a/src/components/domains/DomainGrid.tsx +++ b/src/components/domains/DomainGrid.tsx @@ -1,7 +1,8 @@ import { useState, useEffect } from 'react'; import { usePaginatedApi } from '../../hooks/useApi'; import { getApiEndpointFunctions } from '../../utilities/apiFunctions'; -import { FaEye, FaSearch } from 'react-icons/fa'; +import { FaEye } from 'react-icons/fa'; +import { useDebouncedValue } from '@mantine/hooks'; import { Link } from 'react-router-dom'; import { Card, @@ -15,14 +16,20 @@ import { Pagination, TextInput, } from '@mantine/core'; +import { FaCircleXmark } from 'react-icons/fa6'; const DomainGrid = (): JSX.Element => { const PAGE_SIZE = 12; const [page, setPage] = useState(1); const [maxPages, setMaxPages] = useState(0); + const [search, setSearch] = useState(''); + const [debounce] = useDebouncedValue(search, 200); + const api = getApiEndpointFunctions(); - const { data } = usePaginatedApi(api.domain.get, page, PAGE_SIZE); + const { data } = usePaginatedApi(api.domain.get, page, PAGE_SIZE, { + ...(debounce && { name: debounce }), + }); useEffect(() => { if (data?.count) { @@ -33,28 +40,27 @@ const DomainGrid = (): JSX.Element => { return ( - {data?.results?.length === 0 ? ( - No domains found - ) : ( - - Your Domains - - { - console.log('Search clicked'); - }} - > - - - - )} + + Your Domains + setSearch(e.currentTarget.value)} + /> + setSearch('')} + aria-label="Clear Search" + > + + + + {data?.results?.length === 0 && No domains found} {maxPages !== 1 && ( { const [orderBy, setOrderBy] = useState(''); const [direction, setDirection] = useState('asc'); const [searchTerms, setSearchTerms] = useState>({}); + const [debounce] = useDebouncedValue(searchTerms, 200); const [editingRow, setEditingRow] = useState(null); const [editValues, setEditValues] = useState>({}); @@ -221,7 +223,7 @@ const PaginatedTable = (props: PaginatedTableProps): JSX.Element => { { ...(orderBy ? { order_by: orderBy, direction: direction } : {}), ...Object.fromEntries( - Object.entries(searchTerms).filter(([_, v]) => v), + Object.entries(debounce).filter(([_, v]) => v), ), ...additionalUrlParams, }, diff --git a/src/pages/domains/DomainDetail.tsx b/src/pages/domains/DomainDetail.tsx index a53a231..f723f63 100644 --- a/src/pages/domains/DomainDetail.tsx +++ b/src/pages/domains/DomainDetail.tsx @@ -28,7 +28,7 @@ const DomainDetail = () => { return ( <> - + Add DNS Record From 66458305ad3705a4b92b1f3d40a4f182c1dc2b78 Mon Sep 17 00:00:00 2001 From: Treyson Date: Thu, 22 Aug 2024 11:49:37 -0600 Subject: [PATCH 71/96] Add reload on edit --- src/components/tables/PaginatedTable.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/components/tables/PaginatedTable.tsx b/src/components/tables/PaginatedTable.tsx index 5ecbfec..7d8d426 100644 --- a/src/components/tables/PaginatedTable.tsx +++ b/src/components/tables/PaginatedTable.tsx @@ -216,6 +216,8 @@ const PaginatedTable = (props: PaginatedTableProps): JSX.Element => { const [editingRow, setEditingRow] = useState(null); const [editValues, setEditValues] = useState>({}); + const [reload, setReload] = useState(false); + const { data: paginatedData, loading } = usePaginatedApi( getFunction, page, @@ -225,6 +227,7 @@ const PaginatedTable = (props: PaginatedTableProps): JSX.Element => { ...Object.fromEntries( Object.entries(debounce).filter(([_, v]) => v), ), + ...(reload && { reload: 'true' }), ...additionalUrlParams, }, ); @@ -346,6 +349,7 @@ const PaginatedTable = (props: PaginatedTableProps): JSX.Element => { await updateFunction(editValues); setNotification(['Edit submitted successfully', 'Success']); setEditingRow(null); + setReload((prev) => !prev); } } catch (error) { setNotification([`${error}`, 'Error']); @@ -362,12 +366,13 @@ const PaginatedTable = (props: PaginatedTableProps): JSX.Element => { return ( - + {title} {loading && } {maxPages !== 1 && ( { (editableObj ? 1 : 0) } > - + {loading ? 'Loading...' : data.length === 0 From 7dd8637e2cfd0c7eef86fe9703a2c8fdbf611691 Mon Sep 17 00:00:00 2001 From: Treyson Date: Thu, 22 Aug 2024 11:50:12 -0600 Subject: [PATCH 72/96] Cleaning and initialized add host page --- src/components/LoginForm.tsx | 4 +- src/components/dashboard/Stats.tsx | 4 +- src/components/domains/AddRecordModal.tsx | 1 + src/components/domains/DomainGrid.tsx | 9 ++-- src/components/hosts/AddHost.tsx | 25 ++++++++++ src/components/hosts/AddHostForm.tsx | 58 +++++++++++++++++++++++ src/pages/hosts/AddHost.tsx | 7 +++ src/pages/hosts/AllHosts.tsx | 24 ++++++++-- src/pages/hosts/UserHosts.tsx | 10 ++-- src/routes.tsx | 7 +++ 10 files changed, 135 insertions(+), 14 deletions(-) create mode 100644 src/components/hosts/AddHost.tsx create mode 100644 src/components/hosts/AddHostForm.tsx create mode 100644 src/pages/hosts/AddHost.tsx diff --git a/src/components/LoginForm.tsx b/src/components/LoginForm.tsx index c0552b3..a44d71d 100644 --- a/src/components/LoginForm.tsx +++ b/src/components/LoginForm.tsx @@ -67,8 +67,9 @@ const LoginForm = () => { handleLogin(); }} > - + { size="lg" /> { return ( - Recent Stats + + Recent Stats + {loading && !error && Loading...} {error && Error: {error.message}} {data && ( diff --git a/src/components/domains/AddRecordModal.tsx b/src/components/domains/AddRecordModal.tsx index e94f0c3..c5df33d 100644 --- a/src/components/domains/AddRecordModal.tsx +++ b/src/components/domains/AddRecordModal.tsx @@ -101,6 +101,7 @@ const AddRecordModal = (props: RecordModalProps): JSX.Element => { /> { }); useEffect(() => { - if (data?.count) { + if (data?.count !== undefined) { setMaxPages(Math.ceil(data.count / PAGE_SIZE)); } }, [data]); @@ -44,20 +44,21 @@ const DomainGrid = (): JSX.Element => { Your Domains setSearch(e.currentTarget.value)} + aria-label="Search Domains" /> setSearch('')} aria-label="Clear Search" > - + {data?.results?.length === 0 && No domains found} diff --git a/src/components/hosts/AddHost.tsx b/src/components/hosts/AddHost.tsx new file mode 100644 index 0000000..7e3df12 --- /dev/null +++ b/src/components/hosts/AddHost.tsx @@ -0,0 +1,25 @@ +import { useNavigate } from 'react-router-dom'; + +import { Group, Card, Title, ActionIcon } from '@mantine/core'; +import { FaPlus } from 'react-icons/fa'; + +const AddHost = () => { + const navigate = useNavigate(); + return ( + + + + Add Host + navigate('/hosts/add')} + > + + + + + + ); +}; + +export default AddHost; diff --git a/src/components/hosts/AddHostForm.tsx b/src/components/hosts/AddHostForm.tsx new file mode 100644 index 0000000..e3b4e73 --- /dev/null +++ b/src/components/hosts/AddHostForm.tsx @@ -0,0 +1,58 @@ +import { useForm } from '@mantine/form'; +import { Button, Group, TextInput, Card, Select } from '@mantine/core'; + +const AddHostForm = () => { + const checkMacAddress = (value: string) => { + const macAddressRegex = /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/; + return macAddressRegex.test(value) ? true : false; + }; + + const form = useForm({ + initialValues: { + macAddress: '', + hostname: '', + addressType: '----', + }, + validate: { + macAddress: (value) => + checkMacAddress(value) ? null : 'Invalid MAC Address', + hostname: (value) => + value.trim().length > 0 ? null : 'Invalid Hostname', + }, + }); + + return ( +
console.log(values))}> + + + + { ]} /> + + + + + + + + + )} + + {editableObj && actions.length > 0 && ( + + + + Expiration Date:{' '} + + {data[0]?.expires ? ( + + {formatDate(data[0]?.expires)} + + ) : ( + 'Never' + )} + + + + {renewing && ( +