From 5583714c7a805ce412d9e6d260ebb8ffc3c98ca0 Mon Sep 17 00:00:00 2001 From: Sophia Dao Date: Tue, 25 Jun 2024 13:58:54 -0500 Subject: [PATCH 001/411] feat(poc): Add in current PoC to the repo that will be used by Vercel --- .eslintignore | 20 ++ .eslintrc.json | 122 ++++++++++ .gitignore | 35 +++ .npmrc | 1 + .vscode/settings.json | 3 + Dockerfile | 11 + LICENSE | 21 ++ README.md | 91 ++++++++ app/about/layout.tsx | 13 ++ app/about/page.tsx | 10 + app/blog/layout.tsx | 13 ++ app/blog/page.tsx | 9 + app/clouds/layout.tsx | 13 ++ app/clouds/page.tsx | 51 +++++ app/docs/layout.tsx | 13 ++ app/docs/page.tsx | 9 + app/error.tsx | 31 +++ app/layout.tsx | 54 +++++ app/login/layout.tsx | 13 ++ app/login/page.tsx | 61 +++++ app/page.tsx | 61 +++++ app/pricing/layout.tsx | 13 ++ app/pricing/page.tsx | 9 + app/providers.tsx | 22 ++ be_poc/api/__init__.py | 0 be_poc/api/admin.py | 6 + be_poc/api/apps.py | 6 + be_poc/api/fixtures/dummy_accounts.json | 193 ++++++++++++++++ be_poc/api/migrations/0001_initial.py | 65 ++++++ ..._groups_cloudaccount_resources_and_more.py | 54 +++++ ...003_alter_audit_aws_account_id_and_more.py | 24 ++ ...move_account_billing_cancel_at_and_more.py | 97 ++++++++ be_poc/api/migrations/__init__.py | 0 be_poc/api/models.py | 55 +++++ be_poc/api/serializers.py | 25 ++ be_poc/api/tests.py | 3 + be_poc/api/urls_v1.py | 8 + be_poc/api/views.py | 27 +++ be_poc/be_poc/__init__.py | 0 be_poc/be_poc/asgi.py | 7 + be_poc/be_poc/settings.py | 119 ++++++++++ be_poc/be_poc/urls.py | 7 + be_poc/be_poc/wsgi.py | 7 + be_poc/manage.py | 22 ++ components/counter.tsx | 14 ++ components/icons.tsx | 215 ++++++++++++++++++ components/navbar.tsx | 60 +++++ components/primitives.ts | 53 +++++ components/sidebar.tsx | 0 components/theme-switch.tsx | 81 +++++++ config/fonts.ts | 11 + config/site.ts | 70 ++++++ docker-compose.yaml | 8 + next.config.js | 7 + package.json | 59 +++++ postcss.config.js | 6 + public/favicon.ico | Bin 0 -> 25931 bytes public/next.svg | 1 + public/vercel.svg | 1 + requirements.txt | 3 + styles/globals.css | 3 + tailwind.config.js | 20 ++ tsconfig.json | 41 ++++ types/index.ts | 5 + utils/fetcher.tsx | 1 + 65 files changed, 2083 insertions(+) create mode 100644 .eslintignore create mode 100644 .eslintrc.json create mode 100644 .gitignore create mode 100644 .npmrc create mode 100644 .vscode/settings.json create mode 100644 Dockerfile create mode 100644 LICENSE create mode 100644 README.md create mode 100644 app/about/layout.tsx create mode 100644 app/about/page.tsx create mode 100644 app/blog/layout.tsx create mode 100644 app/blog/page.tsx create mode 100644 app/clouds/layout.tsx create mode 100644 app/clouds/page.tsx create mode 100644 app/docs/layout.tsx create mode 100644 app/docs/page.tsx create mode 100644 app/error.tsx create mode 100644 app/layout.tsx create mode 100644 app/login/layout.tsx create mode 100644 app/login/page.tsx create mode 100644 app/page.tsx create mode 100644 app/pricing/layout.tsx create mode 100644 app/pricing/page.tsx create mode 100644 app/providers.tsx create mode 100644 be_poc/api/__init__.py create mode 100644 be_poc/api/admin.py create mode 100644 be_poc/api/apps.py create mode 100644 be_poc/api/fixtures/dummy_accounts.json create mode 100644 be_poc/api/migrations/0001_initial.py create mode 100644 be_poc/api/migrations/0002_cloudaccount_groups_cloudaccount_resources_and_more.py create mode 100644 be_poc/api/migrations/0003_alter_audit_aws_account_id_and_more.py create mode 100644 be_poc/api/migrations/0004_remove_account_billing_cancel_at_and_more.py create mode 100644 be_poc/api/migrations/__init__.py create mode 100644 be_poc/api/models.py create mode 100644 be_poc/api/serializers.py create mode 100644 be_poc/api/tests.py create mode 100644 be_poc/api/urls_v1.py create mode 100644 be_poc/api/views.py create mode 100644 be_poc/be_poc/__init__.py create mode 100644 be_poc/be_poc/asgi.py create mode 100644 be_poc/be_poc/settings.py create mode 100644 be_poc/be_poc/urls.py create mode 100644 be_poc/be_poc/wsgi.py create mode 100755 be_poc/manage.py create mode 100644 components/counter.tsx create mode 100644 components/icons.tsx create mode 100644 components/navbar.tsx create mode 100644 components/primitives.ts create mode 100644 components/sidebar.tsx create mode 100644 components/theme-switch.tsx create mode 100644 config/fonts.ts create mode 100644 config/site.ts create mode 100644 docker-compose.yaml create mode 100644 next.config.js create mode 100644 package.json create mode 100644 postcss.config.js create mode 100644 public/favicon.ico create mode 100644 public/next.svg create mode 100644 public/vercel.svg create mode 100644 requirements.txt create mode 100644 styles/globals.css create mode 100644 tailwind.config.js create mode 100644 tsconfig.json create mode 100644 types/index.ts create mode 100644 utils/fetcher.tsx diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000000..af6ab76f80 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,20 @@ +.now/* +*.css +.changeset +dist +esm/* +public/* +tests/* +scripts/* +*.config.js +.DS_Store +node_modules +coverage +.next +build +!.commitlintrc.cjs +!.lintstagedrc.cjs +!jest.config.js +!plopfile.js +!react-shim.js +!tsup.config.ts \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000000..f89ad8299f --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,122 @@ +{ + "$schema": "https://json.schemastore.org/eslintrc.json", + "env": { + "browser": false, + "es2021": true, + "node": true + }, + "extends": [ + "plugin:react/recommended", + "plugin:prettier/recommended", + "plugin:react-hooks/recommended", + "plugin:jsx-a11y/recommended", + "next", + "prettier" + ], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaFeatures": { + "jsx": true + }, + "ecmaVersion": 12, + "sourceType": "module" + }, + "plugins": [ + "react", + "unused-imports", + "import", + "@typescript-eslint", + "jsx-a11y", + "prettier" + ], + "rules": { + "@typescript-eslint/no-unused-vars": [ + "warn", + { + "args": "after-used", + "argsIgnorePattern": "^_.*?$", + "ignoreRestSiblings": false + } + ], + "import/order": [ + "warn", + { + "groups": [ + "type", + "builtin", + "object", + "external", + "internal", + "parent", + "sibling", + "index" + ], + "newlines-between": "always", + "pathGroups": [ + { + "group": "external", + "pattern": "~/**", + "position": "after" + } + ] + } + ], + "jsx-a11y/click-events-have-key-events": "warn", + "jsx-a11y/interactive-supports-focus": "warn", + "no-console": "warn", + "no-unused-vars": "off", + "padding-line-between-statements": [ + "warn", + { + "blankLine": "always", + "next": "return", + "prev": "*" + }, + { + "blankLine": "always", + "next": "*", + "prev": [ + "const", + "let", + "var" + ] + }, + { + "blankLine": "any", + "next": [ + "const", + "let", + "var" + ], + "prev": [ + "const", + "let", + "var" + ] + } + ], + "prettier/prettier": "warn", + "react-hooks/exhaustive-deps": "off", + "react/jsx-sort-props": [ + "warn", + { + "callbacksLast": true, + "noSortAlphabetically": true, + "reservedFirst": true, + "shorthandFirst": true + } + ], + "react/jsx-uses-react": "off", + "react/no-unescaped-entities": "off", + "react/prop-types": "off", + "react/react-in-jsx-scope": "off", + "react/self-closing-comp": "warn", + "unused-imports/no-unused-imports": "warn", + "unused-imports/no-unused-vars": "off" + }, + "settings": { + "react": { + "version": "detect" + } + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..8f322f0d8f --- /dev/null +++ b/.gitignore @@ -0,0 +1,35 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000000..43c97e719a --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +package-lock=false diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000000..25fa6215fd --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "typescript.tsdk": "node_modules/typescript/lib" +} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000..2b280ad451 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,11 @@ +FROM python:3.11.9-slim + +COPY requirements.txt /requirements.txt +COPY be_poc /be_poc + +RUN python -m pip install -r /requirements.txt +WORKDIR /be_poc +RUN python manage.py migrate && \ + python manage.py loaddata dummy_accounts + +ENTRYPOINT ["python", "manage.py", "runserver", "0.0.0.0:8000"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000..7f91f8483f --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Next UI + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000000..1843bf36d7 --- /dev/null +++ b/README.md @@ -0,0 +1,91 @@ +# Next.js & NextUI Template + +This is a template for creating applications using Next.js 14 (app directory) and NextUI (v2). + +[Try it on CodeSandbox](https://githubbox.com/nextui-org/next-app-template) + +## Technologies Used + +- [Next.js 14](https://nextjs.org/docs/getting-started) +- [NextUI v2](https://nextui.org/) +- [Tailwind CSS](https://tailwindcss.com/) +- [Tailwind Variants](https://tailwind-variants.org) +- [TypeScript](https://www.typescriptlang.org/) +- [Framer Motion](https://www.framer.com/motion/) +- [next-themes](https://github.com/pacocoursey/next-themes) + +## How to Use + +### Use the template with create-next-app + +To create a new project based on this template using `create-next-app`, run the following command: + +```bash +npx create-next-app -e https://github.com/nextui-org/next-app-template +``` + +### Install dependencies + +You can use one of them `npm`, `yarn`, `pnpm`, `bun`, Example using `npm`: + +```bash +npm install +``` + +### Run the development server + +```bash +npm run dev +``` + +### Setup pnpm (optional) + +If you are using `pnpm`, you need to add the following code to your `.npmrc` file: + +```bash +public-hoist-pattern[]=*@nextui-org/* +``` + +After modifying the `.npmrc` file, you need to run `pnpm install` again to ensure that the dependencies are installed correctly. + +## License + +Licensed under the [MIT license](https://github.com/nextui-org/next-app-template/blob/main/LICENSE). + +# Prowler Django REST API (PoC) + +## Requirements + +- Have `docker` and `docker compose` installed. + +## How to run the REST API + +### Build the service image + +``` +docker compose build django-be-poc +``` + +### Start the service + +``` +docker compose up django-be-poc +``` + +## API + +The API will be accessible through HTTP, port `8080`. For instance, `http://localhost:8080/api/v1/`. + +### Implemented endpoints for the PoC: + +``` +/api/v1/providers/{provider_id}/accounts (the only available provider is 'aws') +``` + +### Expected response with `curl` + +```shellsession +curl http://localhost:8080/api/v1/providers/aws/accounts + +[{"id":1,"type":"Cloudy","enable":true,"provider_id":"aws","provider_data":{"test":"test value"},"inserted_at":"2024-06-24T10:20:18.309000Z","updated_at":"2024-06-24T10:20:18.309000Z","connected":true,"last_checked_at":"2024-06-24T10:20:04Z","alias":"dummy_alias","scanner_configuration":{"full_scan":true},"account_id":1}]% +``` diff --git a/app/about/layout.tsx b/app/about/layout.tsx new file mode 100644 index 0000000000..98956a52ad --- /dev/null +++ b/app/about/layout.tsx @@ -0,0 +1,13 @@ +export default function AboutLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( +
+
+ {children} +
+
+ ); +} diff --git a/app/about/page.tsx b/app/about/page.tsx new file mode 100644 index 0000000000..5a85989de9 --- /dev/null +++ b/app/about/page.tsx @@ -0,0 +1,10 @@ +import { title } from "@/components/primitives"; + +export default function AboutPage() { + return ( +
+

About

+

This is a page with no components on it

+
+ ); +} diff --git a/app/blog/layout.tsx b/app/blog/layout.tsx new file mode 100644 index 0000000000..911d0bcf41 --- /dev/null +++ b/app/blog/layout.tsx @@ -0,0 +1,13 @@ +export default function BlogLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( +
+
+ {children} +
+
+ ); +} diff --git a/app/blog/page.tsx b/app/blog/page.tsx new file mode 100644 index 0000000000..c6d0c65b43 --- /dev/null +++ b/app/blog/page.tsx @@ -0,0 +1,9 @@ +import { title } from "@/components/primitives"; + +export default function BlogPage() { + return ( +
+

Blog

+
+ ); +} diff --git a/app/clouds/layout.tsx b/app/clouds/layout.tsx new file mode 100644 index 0000000000..cab24b4eed --- /dev/null +++ b/app/clouds/layout.tsx @@ -0,0 +1,13 @@ +export default function CloudsLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( +
+
+ {children} +
+
+ ); +} diff --git a/app/clouds/page.tsx b/app/clouds/page.tsx new file mode 100644 index 0000000000..d31830a657 --- /dev/null +++ b/app/clouds/page.tsx @@ -0,0 +1,51 @@ +"use client"; + +import useSWR from "swr"; + +import { + Table, + TableHeader, + TableBody, + TableColumn, + TableRow, + TableCell, +} from "@nextui-org/table"; +import { fetcher } from "@/utils/fetcher"; +import { title } from "@/components/primitives"; + +export default function CloudsPage() { + const { data, error } = useSWR( + `http://localhost:8080/api/v1/providers/aws/accounts`, + fetcher, + ); + + // TODO FIX TYPE CHECKING + const rowItems = data?.map((row: any) => ( + + {row.account_id} + {row.alias} + {row.connected ? "True" : "False"} + + )); + + return ( +
+

Cloud Accounts

+

+ {error && Failed to load} + {!data && Loading} +

+ {data && ( + + + ACCOUNT ID + ALIAS + CONNECTED + + {rowItems} +
+ )} +

This is a page with "use client", useSWR

+
+ ); +} diff --git a/app/docs/layout.tsx b/app/docs/layout.tsx new file mode 100644 index 0000000000..eaf63f3b99 --- /dev/null +++ b/app/docs/layout.tsx @@ -0,0 +1,13 @@ +export default function DocsLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( +
+
+ {children} +
+
+ ); +} diff --git a/app/docs/page.tsx b/app/docs/page.tsx new file mode 100644 index 0000000000..4a2f19b357 --- /dev/null +++ b/app/docs/page.tsx @@ -0,0 +1,9 @@ +import { title } from "@/components/primitives"; + +export default function DocsPage() { + return ( +
+

Docs

+
+ ); +} diff --git a/app/error.tsx b/app/error.tsx new file mode 100644 index 0000000000..9ed5104e8a --- /dev/null +++ b/app/error.tsx @@ -0,0 +1,31 @@ +"use client"; + +import { useEffect } from "react"; + +export default function Error({ + error, + reset, +}: { + error: Error; + reset: () => void; +}) { + useEffect(() => { + // Log the error to an error reporting service + /* eslint-disable no-console */ + console.error(error); + }, [error]); + + return ( +
+

Something went wrong!

+ +
+ ); +} diff --git a/app/layout.tsx b/app/layout.tsx new file mode 100644 index 0000000000..d81ea47ba9 --- /dev/null +++ b/app/layout.tsx @@ -0,0 +1,54 @@ +import { Metadata, Viewport } from "next"; +import clsx from "clsx"; + +import "@/styles/globals.css"; +import { siteConfig } from "@/config/site"; +import { fontSans } from "@/config/fonts"; +import { Navbar } from "@/components/navbar"; + +import { Providers } from "./providers"; + +export const metadata: Metadata = { + title: { + default: siteConfig.name, + template: `%s - ${siteConfig.name}`, + }, + description: siteConfig.description, + icons: { + icon: "/favicon.ico", + }, +}; + +export const viewport: Viewport = { + themeColor: [ + { media: "(prefers-color-scheme: light)", color: "white" }, + { media: "(prefers-color-scheme: dark)", color: "black" }, + ], +}; + +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + + + +
+ +
+ {children} +
+
+
+ + + ); +} diff --git a/app/login/layout.tsx b/app/login/layout.tsx new file mode 100644 index 0000000000..d3a4a58ea4 --- /dev/null +++ b/app/login/layout.tsx @@ -0,0 +1,13 @@ +export default function LoginLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( +
+
+ {children} +
+
+ ); +} diff --git a/app/login/page.tsx b/app/login/page.tsx new file mode 100644 index 0000000000..ee097b2a66 --- /dev/null +++ b/app/login/page.tsx @@ -0,0 +1,61 @@ +"use client"; + +import React from "react"; +import { Card, CardHeader, CardBody, CardFooter } from "@nextui-org/card"; +import { Input } from "@nextui-org/input"; +import { Link } from "@nextui-org/link"; +import { Button } from "@nextui-org/button"; +import { EyeIcon } from "@heroicons/react/24/solid"; +import { EyeSlashIcon } from "@heroicons/react/24/solid"; + +export default function LoginPage() { + const [isVisible, setIsVisible] = React.useState(false); + + const toggleVisibility = () => setIsVisible(!isVisible); + + return ( +
+
+ + +

Login

+
+ + + + {isVisible ? ( + + ) : ( + + )} + + } + type={isVisible ? "text" : "password"} + className="max-w-xs" + /> + + + + +
+

This is a page with "use client", useState

+
+
+ ); +} diff --git a/app/page.tsx b/app/page.tsx new file mode 100644 index 0000000000..c28dfa852c --- /dev/null +++ b/app/page.tsx @@ -0,0 +1,61 @@ +"use client"; + +import React from "react"; +import { Card, CardHeader, CardBody, CardFooter } from "@nextui-org/card"; +import { Input } from "@nextui-org/input"; +import { Link } from "@nextui-org/link"; +import { Button } from "@nextui-org/button"; +import { EyeIcon } from "@heroicons/react/24/solid"; +import { EyeSlashIcon } from "@heroicons/react/24/solid"; + +export default function Home() { + const [isVisible, setIsVisible] = React.useState(false); + + const toggleVisibility = () => setIsVisible(!isVisible); + + return ( +
+
+ + +

Login

+
+ + + + {isVisible ? ( + + ) : ( + + )} + + } + type={isVisible ? "text" : "password"} + className="max-w-xs" + /> + + + + +
+

This is a page with "use client", useState

+
+
+ ); +} diff --git a/app/pricing/layout.tsx b/app/pricing/layout.tsx new file mode 100644 index 0000000000..dc3db6af64 --- /dev/null +++ b/app/pricing/layout.tsx @@ -0,0 +1,13 @@ +export default function PricingLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( +
+
+ {children} +
+
+ ); +} diff --git a/app/pricing/page.tsx b/app/pricing/page.tsx new file mode 100644 index 0000000000..42e233392d --- /dev/null +++ b/app/pricing/page.tsx @@ -0,0 +1,9 @@ +import { title } from "@/components/primitives"; + +export default function PricingPage() { + return ( +
+

Pricing

+
+ ); +} diff --git a/app/providers.tsx b/app/providers.tsx new file mode 100644 index 0000000000..9a1ac92509 --- /dev/null +++ b/app/providers.tsx @@ -0,0 +1,22 @@ +"use client"; + +import * as React from "react"; +import { NextUIProvider } from "@nextui-org/system"; +import { useRouter } from "next/navigation"; +import { ThemeProvider as NextThemesProvider } from "next-themes"; +import { ThemeProviderProps } from "next-themes/dist/types"; + +export interface ProvidersProps { + children: React.ReactNode; + themeProps?: ThemeProviderProps; +} + +export function Providers({ children, themeProps }: ProvidersProps) { + const router = useRouter(); + + return ( + + {children} + + ); +} diff --git a/be_poc/api/__init__.py b/be_poc/api/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/be_poc/api/admin.py b/be_poc/api/admin.py new file mode 100644 index 0000000000..1132d5e64b --- /dev/null +++ b/be_poc/api/admin.py @@ -0,0 +1,6 @@ +from django.contrib import admin +from .models import Account, CloudAccount, Audit + +admin.site.register(Account) +admin.site.register(CloudAccount) +admin.site.register(Audit) diff --git a/be_poc/api/apps.py b/be_poc/api/apps.py new file mode 100644 index 0000000000..66656fd29b --- /dev/null +++ b/be_poc/api/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class ApiConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'api' diff --git a/be_poc/api/fixtures/dummy_accounts.json b/be_poc/api/fixtures/dummy_accounts.json new file mode 100644 index 0000000000..018b66c935 --- /dev/null +++ b/be_poc/api/fixtures/dummy_accounts.json @@ -0,0 +1,193 @@ +[ + { + "model": "api.account", + "pk": "c0bbdcd2-c69e-4bdf-bea7-8db6524fd205", + "fields": { + "name": "prod", + "inserted_at": "2024-06-25T09:03:20.273Z", + "updated_at": "2024-06-25T09:03:20.273Z", + "aws_account_id": "1234567890", + "scan_window_start_at": "2024-06-29T09:03:17Z" + } + }, + { + "model": "api.account", + "pk": "cf51b7a0-687e-4a2a-bf3d-14f98dcb4c3f", + "fields": { + "name": "platform", + "inserted_at": "2024-06-25T09:03:08.847Z", + "updated_at": "2024-06-25T09:03:08.847Z", + "aws_account_id": "1234567890", + "scan_window_start_at": "2024-06-28T09:03:08Z" + } + }, + { + "model": "api.account", + "pk": "cf9d2400-9a10-4c7b-a3be-6215c41b619e", + "fields": { + "name": "dev", + "inserted_at": "2024-06-25T09:02:34.188Z", + "updated_at": "2024-06-25T09:02:34.188Z", + "aws_account_id": "12345467890", + "scan_window_start_at": "2024-06-29T09:02:27Z" + } + }, + { + "model": "api.account", + "pk": "dbc452bd-b51c-4199-8b42-c1bc8164d45d", + "fields": { + "name": "marketplace", + "inserted_at": "2024-06-25T09:02:58.412Z", + "updated_at": "2024-06-25T09:02:58.412Z", + "aws_account_id": "1234567890", + "scan_window_start_at": "2024-06-27T09:02:57Z" + } + }, + { + "model": "api.account", + "pk": "fabc5b58-901b-4ba6-9901-4b2349acf492", + "fields": { + "name": "management", + "inserted_at": "2024-06-25T09:02:46.950Z", + "updated_at": "2024-06-25T09:02:46.950Z", + "aws_account_id": "1234567890", + "scan_window_start_at": "2024-06-27T09:02:46Z" + } + }, + { + "model": "api.cloudaccount", + "pk": "1f1dd221-6d49-4413-a8b7-e10ca4b0660a", + "fields": { + "account_id": "cf9d2400-9a10-4c7b-a3be-6215c41b619e", + "type": "test", + "groups": [ + "Operation" + ], + "resources": 644, + "enable": true, + "provider_id": "aws", + "inserted_at": "2024-06-25T09:03:57.233Z", + "updated_at": "2024-06-25T09:03:57.233Z" + } + }, + { + "model": "api.cloudaccount", + "pk": "4997b2eb-a9f9-4d12-800b-1a6394728510", + "fields": { + "account_id": "c0bbdcd2-c69e-4bdf-bea7-8db6524fd205", + "type": "test", + "groups": [ + "Production" + ], + "resources": 564, + "enable": true, + "provider_id": "aws", + "inserted_at": "2024-06-25T09:04:56.941Z", + "updated_at": "2024-06-25T09:04:56.941Z" + } + }, + { + "model": "api.cloudaccount", + "pk": "668bb77d-cfb4-4f4a-8c51-45beb16af4e9", + "fields": { + "account_id": "dbc452bd-b51c-4199-8b42-c1bc8164d45d", + "type": "test", + "groups": [ + "Production" + ], + "resources": 148, + "enable": true, + "provider_id": "aws", + "inserted_at": "2024-06-25T09:04:31.753Z", + "updated_at": "2024-06-25T09:04:31.753Z" + } + }, + { + "model": "api.cloudaccount", + "pk": "a50d3d9d-ac2d-47d1-ac6c-29c8d304072e", + "fields": { + "account_id": "fabc5b58-901b-4ba6-9901-4b2349acf492", + "type": "test", + "groups": [ + "Production", + "Operation" + ], + "resources": 356, + "enable": true, + "provider_id": "aws", + "inserted_at": "2024-06-25T09:04:14.567Z", + "updated_at": "2024-06-25T09:04:14.567Z" + } + }, + { + "model": "api.cloudaccount", + "pk": "d71c8ac6-074d-45a3-a7a7-0595ab5440bc", + "fields": { + "account_id": "cf51b7a0-687e-4a2a-bf3d-14f98dcb4c3f", + "type": "test", + "groups": [ + "Production" + ], + "resources": 112, + "enable": true, + "provider_id": "aws", + "inserted_at": "2024-06-25T09:04:44.431Z", + "updated_at": "2024-06-25T09:04:44.431Z" + } + }, + { + "model": "api.audit", + "pk": "0f712e8e-b419-40b4-9d85-65ef723fc1b9", + "fields": { + "aws_account_id": "fabc5b58-901b-4ba6-9901-4b2349acf492", + "audit_complete": true, + "inserted_at": "2024-06-25T09:05:37.352Z", + "updated_at": "2024-06-25T09:05:37.352Z", + "audit_duration": "00:06:30" + } + }, + { + "model": "api.audit", + "pk": "1bf18315-34e6-4912-8e8e-3e4650e4653e", + "fields": { + "aws_account_id": "dbc452bd-b51c-4199-8b42-c1bc8164d45d", + "audit_complete": true, + "inserted_at": "2024-06-25T09:05:43.601Z", + "updated_at": "2024-06-25T09:05:43.601Z", + "audit_duration": "00:08:00" + } + }, + { + "model": "api.audit", + "pk": "48605239-58ee-4dbf-8a46-3ef42b270a19", + "fields": { + "aws_account_id": "cf9d2400-9a10-4c7b-a3be-6215c41b619e", + "audit_complete": true, + "inserted_at": "2024-06-25T09:05:29.533Z", + "updated_at": "2024-06-25T09:05:29.533Z", + "audit_duration": "00:06:12" + } + }, + { + "model": "api.audit", + "pk": "955c1a4f-496a-4df5-96c8-578280b7a914", + "fields": { + "aws_account_id": "cf51b7a0-687e-4a2a-bf3d-14f98dcb4c3f", + "audit_complete": true, + "inserted_at": "2024-06-25T09:05:49.405Z", + "updated_at": "2024-06-25T09:05:49.405Z", + "audit_duration": "00:06:40" + } + }, + { + "model": "api.audit", + "pk": "ccbedd87-d64e-42dd-86d0-15ac60ade56d", + "fields": { + "aws_account_id": "c0bbdcd2-c69e-4bdf-bea7-8db6524fd205", + "audit_complete": true, + "inserted_at": "2024-06-25T09:06:00.463Z", + "updated_at": "2024-06-25T09:06:00.463Z", + "audit_duration": "00:07:12" + } + } +] diff --git a/be_poc/api/migrations/0001_initial.py b/be_poc/api/migrations/0001_initial.py new file mode 100644 index 0000000000..5960d1d738 --- /dev/null +++ b/be_poc/api/migrations/0001_initial.py @@ -0,0 +1,65 @@ +# Generated by Django 5.0.6 on 2024-06-24 10:11 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Account', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=255)), + ('token', models.CharField(max_length=255)), + ('inserted_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('card_brand', models.CharField(max_length=255)), + ('card_exp_month', models.IntegerField()), + ('card_exp_year', models.IntegerField()), + ('card_last4', models.CharField(max_length=4)), + ('onboarding_required', models.BooleanField()), + ('onboarding_step', models.CharField(max_length=255)), + ('onboarding_completed_at', models.DateTimeField()), + ('aws_account_id', models.CharField(max_length=255)), + ('off_boarded', models.BooleanField()), + ('billing_type', models.CharField(max_length=255)), + ('billing_cancel_at', models.DateTimeField()), + ('billing_current_period_end_at', models.DateTimeField()), + ('billing_free_resource_count', models.IntegerField()), + ('grafana_org_name', models.CharField(max_length=255)), + ('billing_email', models.CharField(max_length=255)), + ('scan_window_start_at', models.DateTimeField()), + ('company_name', models.CharField(max_length=255)), + ], + options={ + 'db_table': 'accounts', + }, + ), + migrations.CreateModel( + name='CloudAccount', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('type', models.CharField(max_length=255)), + ('enable', models.BooleanField()), + ('provider_id', models.CharField(max_length=255)), + ('provider_data', models.JSONField()), + ('inserted_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('connected', models.BooleanField()), + ('last_checked_at', models.DateTimeField()), + ('alias', models.CharField(max_length=255)), + ('scanner_configuration', models.JSONField()), + ('account_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.account')), + ], + options={ + 'db_table': 'cloud_accounts', + }, + ), + ] diff --git a/be_poc/api/migrations/0002_cloudaccount_groups_cloudaccount_resources_and_more.py b/be_poc/api/migrations/0002_cloudaccount_groups_cloudaccount_resources_and_more.py new file mode 100644 index 0000000000..20586a6a14 --- /dev/null +++ b/be_poc/api/migrations/0002_cloudaccount_groups_cloudaccount_resources_and_more.py @@ -0,0 +1,54 @@ +# Generated by Django 5.0.6 on 2024-06-25 08:56 + +import django.db.models.deletion +import uuid +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='cloudaccount', + name='groups', + field=models.JSONField(default=list), + ), + migrations.AddField( + model_name='cloudaccount', + name='resources', + field=models.IntegerField(default=0), + ), + migrations.AlterField( + model_name='account', + name='id', + field=models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False), + ), + migrations.AlterField( + model_name='cloudaccount', + name='account_id', + field=models.ForeignKey(db_column='account_id', on_delete=django.db.models.deletion.CASCADE, to='api.account', unique=True), + ), + migrations.AlterField( + model_name='cloudaccount', + name='id', + field=models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False), + ), + migrations.CreateModel( + name='Audit', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('audit_complete', models.BooleanField(default=True)), + ('inserted_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('audit_duration', models.DurationField()), + ('aws_account_id', models.ForeignKey(db_column='aws_account_id', on_delete=django.db.models.deletion.CASCADE, to='api.cloudaccount', to_field='account_id', unique=True)), + ], + options={ + 'db_table': 'audits', + }, + ), + ] diff --git a/be_poc/api/migrations/0003_alter_audit_aws_account_id_and_more.py b/be_poc/api/migrations/0003_alter_audit_aws_account_id_and_more.py new file mode 100644 index 0000000000..971d1370b6 --- /dev/null +++ b/be_poc/api/migrations/0003_alter_audit_aws_account_id_and_more.py @@ -0,0 +1,24 @@ +# Generated by Django 5.0.6 on 2024-06-25 08:57 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0002_cloudaccount_groups_cloudaccount_resources_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='audit', + name='aws_account_id', + field=models.OneToOneField(db_column='aws_account_id', on_delete=django.db.models.deletion.CASCADE, to='api.cloudaccount', to_field='account_id'), + ), + migrations.AlterField( + model_name='cloudaccount', + name='account_id', + field=models.OneToOneField(db_column='account_id', on_delete=django.db.models.deletion.CASCADE, to='api.account'), + ), + ] diff --git a/be_poc/api/migrations/0004_remove_account_billing_cancel_at_and_more.py b/be_poc/api/migrations/0004_remove_account_billing_cancel_at_and_more.py new file mode 100644 index 0000000000..89ad02512d --- /dev/null +++ b/be_poc/api/migrations/0004_remove_account_billing_cancel_at_and_more.py @@ -0,0 +1,97 @@ +# Generated by Django 5.0.6 on 2024-06-25 09:01 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0003_alter_audit_aws_account_id_and_more'), + ] + + operations = [ + migrations.RemoveField( + model_name='account', + name='billing_cancel_at', + ), + migrations.RemoveField( + model_name='account', + name='billing_current_period_end_at', + ), + migrations.RemoveField( + model_name='account', + name='billing_email', + ), + migrations.RemoveField( + model_name='account', + name='billing_free_resource_count', + ), + migrations.RemoveField( + model_name='account', + name='billing_type', + ), + migrations.RemoveField( + model_name='account', + name='card_brand', + ), + migrations.RemoveField( + model_name='account', + name='card_exp_month', + ), + migrations.RemoveField( + model_name='account', + name='card_exp_year', + ), + migrations.RemoveField( + model_name='account', + name='card_last4', + ), + migrations.RemoveField( + model_name='account', + name='company_name', + ), + migrations.RemoveField( + model_name='account', + name='grafana_org_name', + ), + migrations.RemoveField( + model_name='account', + name='off_boarded', + ), + migrations.RemoveField( + model_name='account', + name='onboarding_completed_at', + ), + migrations.RemoveField( + model_name='account', + name='onboarding_required', + ), + migrations.RemoveField( + model_name='account', + name='onboarding_step', + ), + migrations.RemoveField( + model_name='account', + name='token', + ), + migrations.RemoveField( + model_name='cloudaccount', + name='alias', + ), + migrations.RemoveField( + model_name='cloudaccount', + name='connected', + ), + migrations.RemoveField( + model_name='cloudaccount', + name='last_checked_at', + ), + migrations.RemoveField( + model_name='cloudaccount', + name='provider_data', + ), + migrations.RemoveField( + model_name='cloudaccount', + name='scanner_configuration', + ), + ] diff --git a/be_poc/api/migrations/__init__.py b/be_poc/api/migrations/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/be_poc/api/models.py b/be_poc/api/models.py new file mode 100644 index 0000000000..3fc3ba9278 --- /dev/null +++ b/be_poc/api/models.py @@ -0,0 +1,55 @@ +import uuid + +from django.db import models + + +class Account(models.Model): + class Meta: + db_table = "accounts" + + id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + name = models.CharField(max_length=255) + inserted_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + aws_account_id = models.CharField(max_length=255) + scan_window_start_at = models.DateTimeField() + + def __str__(self): + return f"{self.name} - {self.id}" + + +class CloudAccount(models.Model): + class Meta: + db_table = "cloud_accounts" + + id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + account_id = models.OneToOneField( + Account, on_delete=models.CASCADE, db_column="account_id", to_field="id" + ) + type = models.CharField(max_length=255) + groups = models.JSONField(default=list) + resources = models.IntegerField(default=0) + enable = models.BooleanField() + provider_id = models.CharField(max_length=255) + inserted_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + def __str__(self): + return f"{self.id} ({self.provider_id})" + + +class Audit(models.Model): + class Meta: + db_table = "audits" + + id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + aws_account_id = models.OneToOneField( + CloudAccount, on_delete=models.CASCADE, db_column="aws_account_id", to_field="account_id" + ) + audit_complete = models.BooleanField(default=True) + inserted_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + audit_duration = models.DurationField() + + def __str__(self): + return f"{self.id} - {self.aws_account_id}" diff --git a/be_poc/api/serializers.py b/be_poc/api/serializers.py new file mode 100644 index 0000000000..2d7a14272f --- /dev/null +++ b/be_poc/api/serializers.py @@ -0,0 +1,25 @@ +from rest_framework import serializers +from .models import Account, CloudAccount, Audit + + +class AccountSerializer(serializers.ModelSerializer): + class Meta: + model = Account + fields = "__all__" + + +class CloudAccountSerializer(serializers.ModelSerializer): + class Meta: + model = CloudAccount + fields = "__all__" + + +class AuditSerializer(serializers.ModelSerializer): + account_id = serializers.SerializerMethodField() + + class Meta: + model = Audit + fields = ['id', 'audit_complete', 'inserted_at', 'updated_at', 'audit_duration', 'account_id'] + + def get_account_id(self, obj): + return obj.aws_account_id.account_id.id diff --git a/be_poc/api/tests.py b/be_poc/api/tests.py new file mode 100644 index 0000000000..7ce503c2dd --- /dev/null +++ b/be_poc/api/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/be_poc/api/urls_v1.py b/be_poc/api/urls_v1.py new file mode 100644 index 0000000000..9386c301dc --- /dev/null +++ b/be_poc/api/urls_v1.py @@ -0,0 +1,8 @@ +from django.urls import path + +from .views import AuditListView, CloudAccountListView + +urlpatterns = [ + path('providers//accounts', CloudAccountListView.as_view(), name='cloud-account-list'), + path('providers/aws/audits', AuditListView.as_view(), name='aws-audit-list'), +] diff --git a/be_poc/api/views.py b/be_poc/api/views.py new file mode 100644 index 0000000000..ebbddb1450 --- /dev/null +++ b/be_poc/api/views.py @@ -0,0 +1,27 @@ +from rest_framework import generics +from .models import Account, CloudAccount, Audit +from .serializers import AccountSerializer, CloudAccountSerializer, AuditSerializer + + +class AccountListView(generics.ListAPIView): + queryset = Account.objects.all() + serializer_class = AccountSerializer + + +class CloudAccountListView(generics.ListAPIView): + serializer_class = CloudAccountSerializer + + def get_queryset(self): + provider_id = self.kwargs.get('provider_id') + return CloudAccount.objects.filter(provider_id=provider_id) + + +class AuditListView(generics.ListAPIView): + serializer_class = AuditSerializer + + def get_queryset(self): + queryset = Audit.objects.all() + account_id = self.request.query_params.get('account_id') + if account_id: + queryset = queryset.filter(aws_account_id__account_id=account_id) + return queryset diff --git a/be_poc/be_poc/__init__.py b/be_poc/be_poc/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/be_poc/be_poc/asgi.py b/be_poc/be_poc/asgi.py new file mode 100644 index 0000000000..2b8704ac78 --- /dev/null +++ b/be_poc/be_poc/asgi.py @@ -0,0 +1,7 @@ +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'be_poc.settings') + +application = get_asgi_application() diff --git a/be_poc/be_poc/settings.py b/be_poc/be_poc/settings.py new file mode 100644 index 0000000000..c55e3e7e1c --- /dev/null +++ b/be_poc/be_poc/settings.py @@ -0,0 +1,119 @@ +from pathlib import Path + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = 'django-insecure-h72a3mgf$l$1uqnjf0bbbz9fbmuotlff7ya50+hlao)fga=3dp' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = [] + + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + + 'rest_framework', + 'api', + 'corsheaders', +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', + + 'corsheaders.middleware.CorsMiddleware' +] + +CORS_ALLOW_ALL_ORIGINS = True + +ROOT_URLCONF = 'be_poc.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'be_poc.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/5.0/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': BASE_DIR / 'db.sqlite3', + } +} + + +# Password validation +# https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/5.0/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/5.0/howto/static-files/ + +STATIC_URL = 'static/' + +# Default primary key field type +# https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' diff --git a/be_poc/be_poc/urls.py b/be_poc/be_poc/urls.py new file mode 100644 index 0000000000..8c12f37f20 --- /dev/null +++ b/be_poc/be_poc/urls.py @@ -0,0 +1,7 @@ +from django.contrib import admin +from django.urls import path, include + +urlpatterns = [ + path('admin/', admin.site.urls), + path('api/v1/', include('api.urls_v1')), +] diff --git a/be_poc/be_poc/wsgi.py b/be_poc/be_poc/wsgi.py new file mode 100644 index 0000000000..0a66b5cc5e --- /dev/null +++ b/be_poc/be_poc/wsgi.py @@ -0,0 +1,7 @@ +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'be_poc.settings') + +application = get_wsgi_application() diff --git a/be_poc/manage.py b/be_poc/manage.py new file mode 100755 index 0000000000..b07263b7cb --- /dev/null +++ b/be_poc/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'be_poc.settings') + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == '__main__': + main() diff --git a/components/counter.tsx b/components/counter.tsx new file mode 100644 index 0000000000..d16f862269 --- /dev/null +++ b/components/counter.tsx @@ -0,0 +1,14 @@ +"use client"; + +import { useState } from "react"; +import { Button } from "@nextui-org/button"; + +export const Counter = () => { + const [count, setCount] = useState(0); + + return ( + + ); +}; diff --git a/components/icons.tsx b/components/icons.tsx new file mode 100644 index 0000000000..a3d73323b4 --- /dev/null +++ b/components/icons.tsx @@ -0,0 +1,215 @@ +import * as React from "react"; + +import { IconSvgProps } from "@/types"; + +export const Logo: React.FC = ({ + size = 36, + width, + height, + ...props +}) => ( + + + +); + +export const DiscordIcon: React.FC = ({ + size = 24, + width, + height, + ...props +}) => { + return ( + + + + ); +}; + +export const TwitterIcon: React.FC = ({ + size = 24, + width, + height, + ...props +}) => { + return ( + + + + ); +}; + +export const GithubIcon: React.FC = ({ + size = 24, + width, + height, + ...props +}) => { + return ( + + + + ); +}; + +export const MoonFilledIcon = ({ + size = 24, + width, + height, + ...props +}: IconSvgProps) => ( + +); + +export const SunFilledIcon = ({ + size = 24, + width, + height, + ...props +}: IconSvgProps) => ( + +); + +export const HeartFilledIcon = ({ + size = 24, + width, + height, + ...props +}: IconSvgProps) => ( + +); + +export const SearchIcon = (props: IconSvgProps) => ( + +); + +export const NextUILogo: React.FC = (props) => { + const { width, height = 40 } = props; + + return ( + + + + + + ); +}; diff --git a/components/navbar.tsx b/components/navbar.tsx new file mode 100644 index 0000000000..6bcdaa770e --- /dev/null +++ b/components/navbar.tsx @@ -0,0 +1,60 @@ +"use client"; + +import React from "react"; +import { + Navbar as NextUINavbar, + NavbarContent, + NavbarItem, + NavbarMenu, + NavbarMenuToggle, + NavbarBrand, + NavbarMenuItem, +} from "@nextui-org/navbar"; +import { Link } from "@nextui-org/link"; + +export const Navbar = () => { + const [isMenuOpen, setIsMenuOpen] = React.useState(false); + + const menuItems = ["Test Link", "Log Out"]; + + return ( + + + + +

PROWLER

+
+
+ + + + + Login + + + + + Cloud Accounts + + + + + About + + + + + + {menuItems.map((item, index) => ( + + + {item} + + + ))} + +
+ ); +}; diff --git a/components/primitives.ts b/components/primitives.ts new file mode 100644 index 0000000000..472973cbe9 --- /dev/null +++ b/components/primitives.ts @@ -0,0 +1,53 @@ +import { tv } from "tailwind-variants"; + +export const title = tv({ + base: "tracking-tight inline font-semibold", + variants: { + color: { + violet: "from-[#FF1CF7] to-[#b249f8]", + yellow: "from-[#FF705B] to-[#FFB457]", + blue: "from-[#5EA2EF] to-[#0072F5]", + cyan: "from-[#00b7fa] to-[#01cfea]", + green: "from-[#6FEE8D] to-[#17c964]", + pink: "from-[#FF72E1] to-[#F54C7A]", + foreground: "dark:from-[#FFFFFF] dark:to-[#4B4B4B]", + }, + size: { + sm: "text-3xl lg:text-4xl", + md: "text-[2.3rem] lg:text-5xl leading-9", + lg: "text-4xl lg:text-6xl", + }, + fullWidth: { + true: "w-full block", + }, + }, + defaultVariants: { + size: "md", + }, + compoundVariants: [ + { + color: [ + "violet", + "yellow", + "blue", + "cyan", + "green", + "pink", + "foreground", + ], + class: "bg-clip-text text-transparent bg-gradient-to-b", + }, + ], +}); + +export const subtitle = tv({ + base: "w-full md:w-1/2 my-2 text-lg lg:text-xl text-default-600 block max-w-full", + variants: { + fullWidth: { + true: "!w-full", + }, + }, + defaultVariants: { + fullWidth: true, + }, +}); diff --git a/components/sidebar.tsx b/components/sidebar.tsx new file mode 100644 index 0000000000..e69de29bb2 diff --git a/components/theme-switch.tsx b/components/theme-switch.tsx new file mode 100644 index 0000000000..55bcb0f329 --- /dev/null +++ b/components/theme-switch.tsx @@ -0,0 +1,81 @@ +"use client"; + +import { FC } from "react"; +import { VisuallyHidden } from "@react-aria/visually-hidden"; +import { SwitchProps, useSwitch } from "@nextui-org/switch"; +import { useTheme } from "next-themes"; +import { useIsSSR } from "@react-aria/ssr"; +import clsx from "clsx"; + +import { SunFilledIcon, MoonFilledIcon } from "@/components/icons"; + +export interface ThemeSwitchProps { + className?: string; + classNames?: SwitchProps["classNames"]; +} + +export const ThemeSwitch: FC = ({ + className, + classNames, +}) => { + const { theme, setTheme } = useTheme(); + const isSSR = useIsSSR(); + + const onChange = () => { + theme === "light" ? setTheme("dark") : setTheme("light"); + }; + + const { + Component, + slots, + isSelected, + getBaseProps, + getInputProps, + getWrapperProps, + } = useSwitch({ + isSelected: theme === "light" || isSSR, + "aria-label": `Switch to ${theme === "light" || isSSR ? "dark" : "light"} mode`, + onChange, + }); + + return ( + + + + +
+ {!isSelected || isSSR ? ( + + ) : ( + + )} +
+
+ ); +}; diff --git a/config/fonts.ts b/config/fonts.ts new file mode 100644 index 0000000000..0e7d9c9422 --- /dev/null +++ b/config/fonts.ts @@ -0,0 +1,11 @@ +import { Fira_Code as FontMono, Inter as FontSans } from "next/font/google"; + +export const fontSans = FontSans({ + subsets: ["latin"], + variable: "--font-sans", +}); + +export const fontMono = FontMono({ + subsets: ["latin"], + variable: "--font-mono", +}); diff --git a/config/site.ts b/config/site.ts new file mode 100644 index 0000000000..23fcc45378 --- /dev/null +++ b/config/site.ts @@ -0,0 +1,70 @@ +export type SiteConfig = typeof siteConfig; + +export const siteConfig = { + name: "Prowler", + description: + "The most comprehensive, free tool for AWS security. ProwlerPro is trusted by leading organizations to make cloud security effortless.", + navItems: [ + { + label: "Home", + href: "/", + }, + { + label: "Docs", + href: "/docs", + }, + { + label: "Pricing", + href: "/pricing", + }, + { + label: "Blog", + href: "/blog", + }, + { + label: "About", + href: "/about", + }, + ], + navMenuItems: [ + { + label: "Profile", + href: "/profile", + }, + { + label: "Dashboard", + href: "/dashboard", + }, + { + label: "Projects", + href: "/projects", + }, + { + label: "Team", + href: "/team", + }, + { + label: "Calendar", + href: "/calendar", + }, + { + label: "Settings", + href: "/settings", + }, + { + label: "Help & Feedback", + href: "/help-feedback", + }, + { + label: "Logout", + href: "/logout", + }, + ], + links: { + github: "https://github.com/nextui-org/nextui", + twitter: "https://twitter.com/getnextui", + docs: "https://nextui.org", + discord: "https://discord.gg/9b6yyZKmH4", + sponsor: "https://patreon.com/jrgarciadev", + }, +}; diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000000..b50276328f --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,8 @@ +version: '3.7' +services: + django-be-poc: + image: django-be-poc + build: + context: "./" + ports: + - "8080:8000" diff --git a/next.config.js b/next.config.js new file mode 100644 index 0000000000..831c1964aa --- /dev/null +++ b/next.config.js @@ -0,0 +1,7 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + output: "export", + trailingSlash: true, +}; + +module.exports = nextConfig; diff --git a/package.json b/package.json new file mode 100644 index 0000000000..d7fc3d3785 --- /dev/null +++ b/package.json @@ -0,0 +1,59 @@ +{ + "dependencies": { + "@heroicons/react": "^2.1.4", + "@nextui-org/button": "2.0.34", + "@nextui-org/card": "^2.0.31", + "@nextui-org/code": "2.0.29", + "@nextui-org/input": "2.2.2", + "@nextui-org/kbd": "2.0.30", + "@nextui-org/link": "2.0.32", + "@nextui-org/listbox": "2.1.21", + "@nextui-org/navbar": "2.0.33", + "@nextui-org/snippet": "2.0.38", + "@nextui-org/switch": "2.0.31", + "@nextui-org/system": "2.2.1", + "@nextui-org/table": "^2.0.36", + "@nextui-org/theme": "2.2.5", + "@react-aria/ssr": "3.9.4", + "@react-aria/visually-hidden": "3.8.12", + "@types/node": "20.5.7", + "@types/react": "18.3.3", + "@types/react-dom": "18.3.0", + "@typescript-eslint/eslint-plugin": "^7.10.0", + "@typescript-eslint/parser": "^7.10.0", + "autoprefixer": "10.4.19", + "clsx": "2.1.1", + "eslint": "^8.56.0", + "eslint-config-next": "14.2.1", + "eslint-plugin-import": "^2.26.0", + "eslint-plugin-jsx-a11y": "^6.4.1", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-prettier": "^5.1.3", + "eslint-plugin-react": "^7.23.2", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-unused-imports": "^3.2.0", + "framer-motion": "~11.1.1", + "intl-messageformat": "^10.5.0", + "next": "14.2.3", + "next-themes": "^0.2.1", + "postcss": "8.4.38", + "react": "18.3.1", + "react-dom": "18.3.1", + "swr": "^2.2.5", + "tailwind-variants": "0.1.20", + "tailwindcss": "3.4.3", + "typescript": "5.0.4" + }, + "devDependencies": { + "eslint-config-prettier": "^9.1.0" + }, + "name": "next-app-template", + "private": true, + "scripts": { + "build": "next build", + "dev": "next dev", + "lint": "eslint . --ext .ts,.tsx -c .eslintrc.json --fix", + "start": "next start" + }, + "version": "0.0.1" +} diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000000..33ad091d26 --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..718d6fea4835ec2d246af9800eddb7ffb276240c GIT binary patch literal 25931 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 LrJugUA?W`A8`#=m literal 0 HcmV?d00001 diff --git a/public/next.svg b/public/next.svg new file mode 100644 index 0000000000..5174b28c56 --- /dev/null +++ b/public/next.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/vercel.svg b/public/vercel.svg new file mode 100644 index 0000000000..d2f8422273 --- /dev/null +++ b/public/vercel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000000..09fa3683db --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +Django==5.0.6 +djangorestframework==3.15.1 +django-cors-headers==4.3.1 diff --git a/styles/globals.css b/styles/globals.css new file mode 100644 index 0000000000..b5c61c9567 --- /dev/null +++ b/styles/globals.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/tailwind.config.js b/tailwind.config.js new file mode 100644 index 0000000000..03a4e22e67 --- /dev/null +++ b/tailwind.config.js @@ -0,0 +1,20 @@ +import {nextui} from '@nextui-org/theme' + +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: [ + './components/**/*.{js,ts,jsx,tsx,mdx}', + './app/**/*.{js,ts,jsx,tsx,mdx}', + './node_modules/@nextui-org/theme/dist/**/*.{js,ts,jsx,tsx}' + ], + theme: { + extend: { + fontFamily: { + sans: ["var(--font-sans)"], + mono: ["var(--font-geist-mono)"], + }, + }, + }, + darkMode: "class", + plugins: [nextui()], +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000000..4d511afe79 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,41 @@ +{ + "compilerOptions": { + "allowJs": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "incremental": true, + "isolatedModules": true, + "jsx": "preserve", + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "module": "esnext", + "moduleResolution": "node", + "noEmit": true, + "paths": { + "@/*": [ + "./*" + ] + }, + "plugins": [ + { + "name": "next" + } + ], + "resolveJsonModule": true, + "skipLibCheck": true, + "strict": true, + "target": "es5" + }, + "exclude": [ + "node_modules" + ], + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts" + ] +} diff --git a/types/index.ts b/types/index.ts new file mode 100644 index 0000000000..cece4a4775 --- /dev/null +++ b/types/index.ts @@ -0,0 +1,5 @@ +import { SVGProps } from "react"; + +export type IconSvgProps = SVGProps & { + size?: number; +}; diff --git a/utils/fetcher.tsx b/utils/fetcher.tsx new file mode 100644 index 0000000000..50b047c9ad --- /dev/null +++ b/utils/fetcher.tsx @@ -0,0 +1 @@ +export const fetcher = (url: string) => fetch(url).then((res) => res.json()); From d0a931bae8d997500266aeddcb5a1426f42eaacf Mon Sep 17 00:00:00 2001 From: Sophia Dao Date: Tue, 25 Jun 2024 19:36:10 -0500 Subject: [PATCH 002/411] feat(poc): Switch to global next.ui package, update python settings for ngrok - wip, add in next table layout --- app/clouds/layout.tsx | 4 +- app/clouds/page.tsx | 256 +++++++++++++++++++++++++++++++++--- app/login/page.tsx | 13 +- app/page.tsx | 13 +- be_poc/be_poc/settings.py | 2 +- components/counter.tsx | 2 +- components/navbar.tsx | 4 +- components/theme-switch.tsx | 2 +- docker-compose.yaml | 1 - package.json | 12 +- 10 files changed, 265 insertions(+), 44 deletions(-) diff --git a/app/clouds/layout.tsx b/app/clouds/layout.tsx index cab24b4eed..d9ca1e80e4 100644 --- a/app/clouds/layout.tsx +++ b/app/clouds/layout.tsx @@ -5,9 +5,7 @@ export default function CloudsLayout({ }) { return (
-
- {children} -
+
{children}
); } diff --git a/app/clouds/page.tsx b/app/clouds/page.tsx index d31830a657..61212ab268 100644 --- a/app/clouds/page.tsx +++ b/app/clouds/page.tsx @@ -1,6 +1,7 @@ "use client"; import useSWR from "swr"; +import React from "react"; import { Table, @@ -9,40 +10,263 @@ import { TableColumn, TableRow, TableCell, -} from "@nextui-org/table"; + User, + Chip, + Tooltip, + getKeyValue, +} from "@nextui-org/react"; import { fetcher } from "@/utils/fetcher"; import { title } from "@/components/primitives"; +import { PencilSquareIcon } from "@heroicons/react/24/solid"; +import { TrashIcon } from "@heroicons/react/24/solid"; +import { EyeIcon } from "@heroicons/react/24/solid"; + +const statusColorMap = { + active: "success", + paused: "danger", + vacation: "warning", +}; + +const columns = [ + { name: "NAME", uid: "name" }, + { name: "ROLE", uid: "role" }, + { name: "STATUS", uid: "status" }, + { name: "ACTIONS", uid: "actions" }, +]; + +const users = [ + { + id: 1, + name: "Tony Reichert", + role: "CEO", + team: "Management", + status: "active", + age: "29", + avatar: "https://i.pravatar.cc/150?u=a042581f4e29026024d", + email: "tony.reichert@example.com", + }, + { + id: 2, + name: "Zoey Lang", + role: "Technical Lead", + team: "Development", + status: "paused", + age: "25", + avatar: "https://i.pravatar.cc/150?u=a042581f4e29026704d", + email: "zoey.lang@example.com", + }, + { + id: 3, + name: "Jane Fisher", + role: "Senior Developer", + team: "Development", + status: "active", + age: "22", + avatar: "https://i.pravatar.cc/150?u=a04258114e29026702d", + email: "jane.fisher@example.com", + }, + { + id: 4, + name: "William Howard", + role: "Community Manager", + team: "Marketing", + status: "vacation", + age: "28", + avatar: "https://i.pravatar.cc/150?u=a048581f4e29026701d", + email: "william.howard@example.com", + }, + { + id: 5, + name: "Kristen Copper", + role: "Sales Manager", + team: "Sales", + status: "active", + age: "24", + avatar: "https://i.pravatar.cc/150?u=a092581d4ef9026700d", + email: "kristen.cooper@example.com", + }, +]; + export default function CloudsPage() { - const { data, error } = useSWR( + const { data, error, isLoading } = useSWR( `http://localhost:8080/api/v1/providers/aws/accounts`, fetcher, ); + console.log("### data", data); + // TODO FIX TYPE CHECKING - const rowItems = data?.map((row: any) => ( - - {row.account_id} - {row.alias} - {row.connected ? "True" : "False"} - - )); + // const rowItems = data?.map((row: any) => ( + // + // {row.account_id} + // {row.alias} + // {row.connected ? "True" : "False"} + // + // )); + + // console.log("### rows", rows); + + // const rows = data; + + // const rows = [ + // { + // key: "1", + // name: "Tony Reichert", + // role: "CEO", + // status: "Active", + // }, + // { + // key: "2", + // name: "Zoey Lang", + // role: "Technical Lead", + // status: "Paused", + // }, + // { + // key: "3", + // name: "Jane Fisher", + // role: "Senior Developer", + // status: "Active", + // }, + // { + // key: "4", + // name: "William Howard", + // role: "Community Manager", + // status: "Vacation", + // }, + // ]; + + // const columns = [ + // { + // key: "provider_id", + // label: "Account", + // }, + // { + // key: "groups", + // label: "Group(s)", + // }, + // { + // key: "status", + // label: "Scan Status", + // }, + // { + // key: "lastScan", + // label: "Last Scan", + // }, + // { + // key: "nextScan", + // label: "Next Scan", + // }, + // { + // key: "resources", + // label: "Resources", + // }, + // { + // key: "added", + // label: "Added", + // }, + // ]; + + const renderCell = React.useCallback((user, columnKey) => { + const cellValue = user[columnKey]; + + switch (columnKey) { + case "name": + return ( + + {user.email} + + ); + case "role": + return ( +
+

{cellValue}

+

+ {user.team} +

+
+ ); + case "status": + return ( + + {cellValue} + + ); + case "actions": + return ( +
+ + + + + + + + + + + + + + + +
+ ); + default: + return cellValue; + } + }, []); return (

Cloud Accounts

{error && Failed to load} - {!data && Loading} + {isLoading && Loading}

{data && ( - - - ACCOUNT ID - ALIAS - CONNECTED + //
+ // + // ACCOUNT ID + // ALIAS + // CONNECTED + // + // {rowItems} + //
+ + + + {(column) => ( + + {column.name} + + )} - {rowItems} + + {(item) => ( + + {(columnKey) => ( + {renderCell(item, columnKey)} + )} + + )} +
)}

This is a page with "use client", useSWR

diff --git a/app/login/page.tsx b/app/login/page.tsx index ee097b2a66..113f81418e 100644 --- a/app/login/page.tsx +++ b/app/login/page.tsx @@ -1,10 +1,15 @@ "use client"; import React from "react"; -import { Card, CardHeader, CardBody, CardFooter } from "@nextui-org/card"; -import { Input } from "@nextui-org/input"; -import { Link } from "@nextui-org/link"; -import { Button } from "@nextui-org/button"; +import { + Card, + CardHeader, + CardBody, + CardFooter, + Input, + Link, + Button, +} from "@nextui-org/react"; import { EyeIcon } from "@heroicons/react/24/solid"; import { EyeSlashIcon } from "@heroicons/react/24/solid"; diff --git a/app/page.tsx b/app/page.tsx index c28dfa852c..e840155e19 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,10 +1,15 @@ "use client"; import React from "react"; -import { Card, CardHeader, CardBody, CardFooter } from "@nextui-org/card"; -import { Input } from "@nextui-org/input"; -import { Link } from "@nextui-org/link"; -import { Button } from "@nextui-org/button"; +import { + Card, + CardHeader, + CardBody, + CardFooter, + Input, + Link, + Button, +} from "@nextui-org/react"; import { EyeIcon } from "@heroicons/react/24/solid"; import { EyeSlashIcon } from "@heroicons/react/24/solid"; diff --git a/be_poc/be_poc/settings.py b/be_poc/be_poc/settings.py index c55e3e7e1c..f6fb8e2fee 100644 --- a/be_poc/be_poc/settings.py +++ b/be_poc/be_poc/settings.py @@ -13,7 +13,7 @@ SECRET_KEY = 'django-insecure-h72a3mgf$l$1uqnjf0bbbz9fbmuotlff7ya50+hlao)fga=3dp # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True -ALLOWED_HOSTS = [] +ALLOWED_HOSTS = ['localhost', 'steady-separately-marlin.ngrok-free.app'] # Application definition diff --git a/components/counter.tsx b/components/counter.tsx index d16f862269..85cb44dc27 100644 --- a/components/counter.tsx +++ b/components/counter.tsx @@ -1,7 +1,7 @@ "use client"; import { useState } from "react"; -import { Button } from "@nextui-org/button"; +import { Button } from "@nextui-org/react"; export const Counter = () => { const [count, setCount] = useState(0); diff --git a/components/navbar.tsx b/components/navbar.tsx index 6bcdaa770e..a52bd03f3c 100644 --- a/components/navbar.tsx +++ b/components/navbar.tsx @@ -9,8 +9,8 @@ import { NavbarMenuToggle, NavbarBrand, NavbarMenuItem, -} from "@nextui-org/navbar"; -import { Link } from "@nextui-org/link"; + Link, +} from "@nextui-org/react"; export const Navbar = () => { const [isMenuOpen, setIsMenuOpen] = React.useState(false); diff --git a/components/theme-switch.tsx b/components/theme-switch.tsx index 55bcb0f329..b44961bfa0 100644 --- a/components/theme-switch.tsx +++ b/components/theme-switch.tsx @@ -2,7 +2,7 @@ import { FC } from "react"; import { VisuallyHidden } from "@react-aria/visually-hidden"; -import { SwitchProps, useSwitch } from "@nextui-org/switch"; +import { SwitchProps, useSwitch } from "@nextui-org/react"; import { useTheme } from "next-themes"; import { useIsSSR } from "@react-aria/ssr"; import clsx from "clsx"; diff --git a/docker-compose.yaml b/docker-compose.yaml index b50276328f..745691a46c 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,4 +1,3 @@ -version: '3.7' services: django-be-poc: image: django-be-poc diff --git a/package.json b/package.json index d7fc3d3785..158c6fc650 100644 --- a/package.json +++ b/package.json @@ -1,18 +1,8 @@ { "dependencies": { "@heroicons/react": "^2.1.4", - "@nextui-org/button": "2.0.34", - "@nextui-org/card": "^2.0.31", - "@nextui-org/code": "2.0.29", - "@nextui-org/input": "2.2.2", - "@nextui-org/kbd": "2.0.30", - "@nextui-org/link": "2.0.32", - "@nextui-org/listbox": "2.1.21", - "@nextui-org/navbar": "2.0.33", - "@nextui-org/snippet": "2.0.38", - "@nextui-org/switch": "2.0.31", + "@nextui-org/react": "^2.4.2", "@nextui-org/system": "2.2.1", - "@nextui-org/table": "^2.0.36", "@nextui-org/theme": "2.2.5", "@react-aria/ssr": "3.9.4", "@react-aria/visually-hidden": "3.8.12", From 644c4fd3a4876b6d83087c4e19878554fdb4258e Mon Sep 17 00:00:00 2001 From: Sophia Dao Date: Wed, 26 Jun 2024 01:32:45 -0500 Subject: [PATCH 003/411] WIP Hook up API and display data --- app/clouds/page.tsx | 136 ++++++++++++++++++++++++++++++-------------- 1 file changed, 92 insertions(+), 44 deletions(-) diff --git a/app/clouds/page.tsx b/app/clouds/page.tsx index 61212ab268..d7f378b8a1 100644 --- a/app/clouds/page.tsx +++ b/app/clouds/page.tsx @@ -89,21 +89,58 @@ const users = [ ]; export default function CloudsPage() { - const { data, error, isLoading } = useSWR( + const getAccounts = useSWR( `http://localhost:8080/api/v1/providers/aws/accounts`, fetcher, ); - console.log("### data", data); + const getAudits = useSWR( + `http://localhost:8080/api/v1/providers/aws/audits`, + fetcher, + ); // TODO FIX TYPE CHECKING - // const rowItems = data?.map((row: any) => ( - // - // {row.account_id} - // {row.alias} - // {row.connected ? "True" : "False"} - // - // )); + const getScanDetails = (account_id: Number, detail: String) => { + const scan = + getAudits.data && + getAudits.data.find((audit: any) => audit.account_id === account_id); + + console.log("### scan", scan); + console.log("### new date", new Date("2024-06-25T09:05:49.405000Z")); + + if (detail === "status") { + return scan?.audit_complete && "Completed"; + } + + if (detail === "duration") { + return scan?.audit_duration; + } + + if (detail === "added") { + return new Date(scan?.inserted_at).toString(); + } + + return; + }; + + console.log("### getAccounts data", getAccounts.data); + console.log("### getAudits data", getAudits.data); + + // TODO FIX TYPE CHECKING + const rowItems = getAccounts.data?.map((row: any) => ( + + {row.account_id} + {row.provider_id} + TBD + TBD + {row.groups.map(String).join(", ")} + {getScanDetails(row.account_id, "status")} + {getScanDetails(row.account_id, "duration")} + TBD + {row.resources} + {getScanDetails(row.account_id, "added")} + + )); // console.log("### rows", rows); @@ -230,44 +267,55 @@ export default function CloudsPage() {

Cloud Accounts

- {error && Failed to load} - {isLoading && Loading} + {getAccounts.error && ( + Failed to load + )} + {getAccounts.isLoading && ( + Loading + )}

- {data && ( - // - // - // ACCOUNT ID - // ALIAS - // CONNECTED - // - // {rowItems} - //
- - - - {(column) => ( - - {column.name} - - )} + {getAccounts.data && ( +
+ + ACCOUNT ID + PROVIDER ID + ALIAS + CONNECTED + GROUP(S) + SCAN STATUS + LAST SCAN + NEXT SCAN + RESOURCES + ADDED - - {(item) => ( - - {(columnKey) => ( - {renderCell(item, columnKey)} - )} - - )} - + {rowItems}
+ + // + // + // {(column) => ( + // + // {column.name} + // + // )} + // + // + // {(item) => ( + // + // {(columnKey) => ( + // {renderCell(item, columnKey)} + // )} + // + // )} + // + //
)}

This is a page with "use client", useSWR

From 2ff7d81a9b895a59cddd74e15ffb17ea73608d1a Mon Sep 17 00:00:00 2001 From: Sophia Dao Date: Wed, 26 Jun 2024 01:33:56 -0500 Subject: [PATCH 004/411] Comment out console.log --- app/clouds/page.tsx | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/app/clouds/page.tsx b/app/clouds/page.tsx index d7f378b8a1..878d778ed4 100644 --- a/app/clouds/page.tsx +++ b/app/clouds/page.tsx @@ -105,9 +105,6 @@ export default function CloudsPage() { getAudits.data && getAudits.data.find((audit: any) => audit.account_id === account_id); - console.log("### scan", scan); - console.log("### new date", new Date("2024-06-25T09:05:49.405000Z")); - if (detail === "status") { return scan?.audit_complete && "Completed"; } @@ -123,8 +120,8 @@ export default function CloudsPage() { return; }; - console.log("### getAccounts data", getAccounts.data); - console.log("### getAudits data", getAudits.data); + // console.log("### getAccounts data", getAccounts.data); + // console.log("### getAudits data", getAudits.data); // TODO FIX TYPE CHECKING const rowItems = getAccounts.data?.map((row: any) => ( From 385eb5cc18b7558918d62e8165bc720ad00d5f95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Fern=C3=A1ndez=20Poyatos?= Date: Wed, 26 Jun 2024 11:12:00 +0200 Subject: [PATCH 005/411] feat(django-be): update models and serializers --- be_poc/api/fixtures/dummy_accounts.json | 30 ++++++++++++------- ...oudaccount_alias_cloudaccount_connected.py | 23 ++++++++++++++ be_poc/api/models.py | 2 ++ be_poc/api/serializers.py | 6 ++++ 4 files changed, 51 insertions(+), 10 deletions(-) create mode 100644 be_poc/api/migrations/0005_cloudaccount_alias_cloudaccount_connected.py diff --git a/be_poc/api/fixtures/dummy_accounts.json b/be_poc/api/fixtures/dummy_accounts.json index 018b66c935..bc4b04401d 100644 --- a/be_poc/api/fixtures/dummy_accounts.json +++ b/be_poc/api/fixtures/dummy_accounts.json @@ -6,7 +6,7 @@ "name": "prod", "inserted_at": "2024-06-25T09:03:20.273Z", "updated_at": "2024-06-25T09:03:20.273Z", - "aws_account_id": "1234567890", + "aws_account_id": "232136659152", "scan_window_start_at": "2024-06-29T09:03:17Z" } }, @@ -17,7 +17,7 @@ "name": "platform", "inserted_at": "2024-06-25T09:03:08.847Z", "updated_at": "2024-06-25T09:03:08.847Z", - "aws_account_id": "1234567890", + "aws_account_id": "714274078102", "scan_window_start_at": "2024-06-28T09:03:08Z" } }, @@ -28,7 +28,7 @@ "name": "dev", "inserted_at": "2024-06-25T09:02:34.188Z", "updated_at": "2024-06-25T09:02:34.188Z", - "aws_account_id": "12345467890", + "aws_account_id": "106908755756", "scan_window_start_at": "2024-06-29T09:02:27Z" } }, @@ -39,7 +39,7 @@ "name": "marketplace", "inserted_at": "2024-06-25T09:02:58.412Z", "updated_at": "2024-06-25T09:02:58.412Z", - "aws_account_id": "1234567890", + "aws_account_id": "879304169158", "scan_window_start_at": "2024-06-27T09:02:57Z" } }, @@ -50,7 +50,7 @@ "name": "management", "inserted_at": "2024-06-25T09:02:46.950Z", "updated_at": "2024-06-25T09:02:46.950Z", - "aws_account_id": "1234567890", + "aws_account_id": "741399645537", "scan_window_start_at": "2024-06-27T09:02:46Z" } }, @@ -67,7 +67,9 @@ "enable": true, "provider_id": "aws", "inserted_at": "2024-06-25T09:03:57.233Z", - "updated_at": "2024-06-25T09:03:57.233Z" + "updated_at": "2024-06-25T09:03:57.233Z", + "alias": "dev", + "connected": true } }, { @@ -83,7 +85,9 @@ "enable": true, "provider_id": "aws", "inserted_at": "2024-06-25T09:04:56.941Z", - "updated_at": "2024-06-25T09:04:56.941Z" + "updated_at": "2024-06-25T09:04:56.941Z", + "alias": "prod", + "connected": true } }, { @@ -99,7 +103,9 @@ "enable": true, "provider_id": "aws", "inserted_at": "2024-06-25T09:04:31.753Z", - "updated_at": "2024-06-25T09:04:31.753Z" + "updated_at": "2024-06-25T09:04:31.753Z", + "alias": "marketplace", + "connected": true } }, { @@ -116,7 +122,9 @@ "enable": true, "provider_id": "aws", "inserted_at": "2024-06-25T09:04:14.567Z", - "updated_at": "2024-06-25T09:04:14.567Z" + "updated_at": "2024-06-25T09:04:14.567Z", + "alias": "management", + "connected": true } }, { @@ -132,7 +140,9 @@ "enable": true, "provider_id": "aws", "inserted_at": "2024-06-25T09:04:44.431Z", - "updated_at": "2024-06-25T09:04:44.431Z" + "updated_at": "2024-06-25T09:04:44.431Z", + "alias": "platform", + "connected": true } }, { diff --git a/be_poc/api/migrations/0005_cloudaccount_alias_cloudaccount_connected.py b/be_poc/api/migrations/0005_cloudaccount_alias_cloudaccount_connected.py new file mode 100644 index 0000000000..9edec907e3 --- /dev/null +++ b/be_poc/api/migrations/0005_cloudaccount_alias_cloudaccount_connected.py @@ -0,0 +1,23 @@ +# Generated by Django 4.2.13 on 2024-06-26 07:56 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0004_remove_account_billing_cancel_at_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='cloudaccount', + name='alias', + field=models.CharField(max_length=255, null=True), + ), + migrations.AddField( + model_name='cloudaccount', + name='connected', + field=models.BooleanField(default=False), + ), + ] diff --git a/be_poc/api/models.py b/be_poc/api/models.py index 3fc3ba9278..067c24b0d5 100644 --- a/be_poc/api/models.py +++ b/be_poc/api/models.py @@ -30,6 +30,8 @@ class CloudAccount(models.Model): groups = models.JSONField(default=list) resources = models.IntegerField(default=0) enable = models.BooleanField() + alias = models.CharField(max_length=255, null=True) + connected = models.BooleanField(default=False) provider_id = models.CharField(max_length=255) inserted_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) diff --git a/be_poc/api/serializers.py b/be_poc/api/serializers.py index 2d7a14272f..171a69a73c 100644 --- a/be_poc/api/serializers.py +++ b/be_poc/api/serializers.py @@ -9,9 +9,15 @@ class AccountSerializer(serializers.ModelSerializer): class CloudAccountSerializer(serializers.ModelSerializer): + aws_account_id = serializers.SerializerMethodField() + class Meta: model = CloudAccount fields = "__all__" + extra_fields = ["aws_account_id"] + + def get_aws_account_id(self, obj): + return obj.account_id.aws_account_id class AuditSerializer(serializers.ModelSerializer): From ccfc46d7439087a37445c1e48c8465be073eb426 Mon Sep 17 00:00:00 2001 From: Sophia Dao Date: Wed, 26 Jun 2024 08:31:02 -0500 Subject: [PATCH 006/411] feat(poc): Add in more data from api --- app/clouds/page.tsx | 221 +------------------------------------------- 1 file changed, 5 insertions(+), 216 deletions(-) diff --git a/app/clouds/page.tsx b/app/clouds/page.tsx index 878d778ed4..a6f35d6e16 100644 --- a/app/clouds/page.tsx +++ b/app/clouds/page.tsx @@ -22,72 +22,6 @@ import { PencilSquareIcon } from "@heroicons/react/24/solid"; import { TrashIcon } from "@heroicons/react/24/solid"; import { EyeIcon } from "@heroicons/react/24/solid"; -const statusColorMap = { - active: "success", - paused: "danger", - vacation: "warning", -}; - -const columns = [ - { name: "NAME", uid: "name" }, - { name: "ROLE", uid: "role" }, - { name: "STATUS", uid: "status" }, - { name: "ACTIONS", uid: "actions" }, -]; - -const users = [ - { - id: 1, - name: "Tony Reichert", - role: "CEO", - team: "Management", - status: "active", - age: "29", - avatar: "https://i.pravatar.cc/150?u=a042581f4e29026024d", - email: "tony.reichert@example.com", - }, - { - id: 2, - name: "Zoey Lang", - role: "Technical Lead", - team: "Development", - status: "paused", - age: "25", - avatar: "https://i.pravatar.cc/150?u=a042581f4e29026704d", - email: "zoey.lang@example.com", - }, - { - id: 3, - name: "Jane Fisher", - role: "Senior Developer", - team: "Development", - status: "active", - age: "22", - avatar: "https://i.pravatar.cc/150?u=a04258114e29026702d", - email: "jane.fisher@example.com", - }, - { - id: 4, - name: "William Howard", - role: "Community Manager", - team: "Marketing", - status: "vacation", - age: "28", - avatar: "https://i.pravatar.cc/150?u=a048581f4e29026701d", - email: "william.howard@example.com", - }, - { - id: 5, - name: "Kristen Copper", - role: "Sales Manager", - team: "Sales", - status: "active", - age: "24", - avatar: "https://i.pravatar.cc/150?u=a092581d4ef9026700d", - email: "kristen.cooper@example.com", - }, -]; - export default function CloudsPage() { const getAccounts = useSWR( `http://localhost:8080/api/v1/providers/aws/accounts`, @@ -114,7 +48,7 @@ export default function CloudsPage() { } if (detail === "added") { - return new Date(scan?.inserted_at).toString(); + return new Date(scan?.inserted_at).toDateString(); } return; @@ -126,10 +60,10 @@ export default function CloudsPage() { // TODO FIX TYPE CHECKING const rowItems = getAccounts.data?.map((row: any) => ( - {row.account_id} + {row.aws_account_id} {row.provider_id} - TBD - TBD + {row.alias} + {row.connected && "Connected"} {row.groups.map(String).join(", ")} {getScanDetails(row.account_id, "status")} {getScanDetails(row.account_id, "duration")} @@ -139,126 +73,7 @@ export default function CloudsPage() { )); - // console.log("### rows", rows); - - // const rows = data; - - // const rows = [ - // { - // key: "1", - // name: "Tony Reichert", - // role: "CEO", - // status: "Active", - // }, - // { - // key: "2", - // name: "Zoey Lang", - // role: "Technical Lead", - // status: "Paused", - // }, - // { - // key: "3", - // name: "Jane Fisher", - // role: "Senior Developer", - // status: "Active", - // }, - // { - // key: "4", - // name: "William Howard", - // role: "Community Manager", - // status: "Vacation", - // }, - // ]; - - // const columns = [ - // { - // key: "provider_id", - // label: "Account", - // }, - // { - // key: "groups", - // label: "Group(s)", - // }, - // { - // key: "status", - // label: "Scan Status", - // }, - // { - // key: "lastScan", - // label: "Last Scan", - // }, - // { - // key: "nextScan", - // label: "Next Scan", - // }, - // { - // key: "resources", - // label: "Resources", - // }, - // { - // key: "added", - // label: "Added", - // }, - // ]; - - const renderCell = React.useCallback((user, columnKey) => { - const cellValue = user[columnKey]; - - switch (columnKey) { - case "name": - return ( - - {user.email} - - ); - case "role": - return ( -
-

{cellValue}

-

- {user.team} -

-
- ); - case "status": - return ( - - {cellValue} - - ); - case "actions": - return ( -
- - - - - - - - - - - - - - - -
- ); - default: - return cellValue; - } - }, []); + // TODO IMPLEMENT NEXT UI SPECIFIC TABLE COMPONENT WITH VARIED RENDERING return (
@@ -287,32 +102,6 @@ export default function CloudsPage() { {rowItems} - - // - // - // {(column) => ( - // - // {column.name} - // - // )} - // - // - // {(item) => ( - // - // {(columnKey) => ( - // {renderCell(item, columnKey)} - // )} - // - // )} - // - //
)}

This is a page with "use client", useSWR

From c000aa2602f43de81600f6ac112de20c476d9154 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 26 Jun 2024 19:33:40 +0000 Subject: [PATCH 007/411] build(deps): bump djangorestframework from 3.15.1 to 3.15.2 Bumps [djangorestframework](https://github.com/encode/django-rest-framework) from 3.15.1 to 3.15.2. - [Release notes](https://github.com/encode/django-rest-framework/releases) - [Commits](https://github.com/encode/django-rest-framework/compare/3.15.1...3.15.2) --- updated-dependencies: - dependency-name: djangorestframework dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 09fa3683db..e78d72ead8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ Django==5.0.6 -djangorestframework==3.15.1 +djangorestframework==3.15.2 django-cors-headers==4.3.1 From 1637325625d5aa011c182195962466f4640d8287 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Fri, 5 Jul 2024 11:00:42 +0200 Subject: [PATCH 008/411] chore: replace eslintrc.json by eslintrc.cjs --- .eslintrc.cjs | 39 ++++++++++++++++ .eslintrc.json | 122 ------------------------------------------------- 2 files changed, 39 insertions(+), 122 deletions(-) create mode 100644 .eslintrc.cjs delete mode 100644 .eslintrc.json diff --git a/.eslintrc.cjs b/.eslintrc.cjs new file mode 100644 index 0000000000..1843e62bcf --- /dev/null +++ b/.eslintrc.cjs @@ -0,0 +1,39 @@ +module.exports = { + env: { + node: true, + es2021: true, + }, + parser: "@typescript-eslint/parser", + plugins: ["prettier", "@typescript-eslint", "simple-import-sort"], + extends: [ + "eslint:recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended", + "prettier", + ], + parserOptions: { + ecmaVersion: "latest", + sourceType: "module", + ecmaFeatures: { + jsx: true, + }, + }, + rules: { + "no-console": 1, + eqeqeq: 2, + // indent: ["error", 2, { SwitchCase: 1 }], // disabled because it clashes with prettier's indent + quotes: ["error", "double", "avoid-escape"], + "@typescript-eslint/no-explicit-any": "off", + "prettier/prettier": [ + "error", + { + endOfLine: "auto", + tabWidth: 2, + useTabs: false, + }, + ], + "eol-last": ["error", "always"], + "simple-import-sort/imports": "error", + "simple-import-sort/exports": "error", + }, +}; diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index f89ad8299f..0000000000 --- a/.eslintrc.json +++ /dev/null @@ -1,122 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/eslintrc.json", - "env": { - "browser": false, - "es2021": true, - "node": true - }, - "extends": [ - "plugin:react/recommended", - "plugin:prettier/recommended", - "plugin:react-hooks/recommended", - "plugin:jsx-a11y/recommended", - "next", - "prettier" - ], - "parser": "@typescript-eslint/parser", - "parserOptions": { - "ecmaFeatures": { - "jsx": true - }, - "ecmaVersion": 12, - "sourceType": "module" - }, - "plugins": [ - "react", - "unused-imports", - "import", - "@typescript-eslint", - "jsx-a11y", - "prettier" - ], - "rules": { - "@typescript-eslint/no-unused-vars": [ - "warn", - { - "args": "after-used", - "argsIgnorePattern": "^_.*?$", - "ignoreRestSiblings": false - } - ], - "import/order": [ - "warn", - { - "groups": [ - "type", - "builtin", - "object", - "external", - "internal", - "parent", - "sibling", - "index" - ], - "newlines-between": "always", - "pathGroups": [ - { - "group": "external", - "pattern": "~/**", - "position": "after" - } - ] - } - ], - "jsx-a11y/click-events-have-key-events": "warn", - "jsx-a11y/interactive-supports-focus": "warn", - "no-console": "warn", - "no-unused-vars": "off", - "padding-line-between-statements": [ - "warn", - { - "blankLine": "always", - "next": "return", - "prev": "*" - }, - { - "blankLine": "always", - "next": "*", - "prev": [ - "const", - "let", - "var" - ] - }, - { - "blankLine": "any", - "next": [ - "const", - "let", - "var" - ], - "prev": [ - "const", - "let", - "var" - ] - } - ], - "prettier/prettier": "warn", - "react-hooks/exhaustive-deps": "off", - "react/jsx-sort-props": [ - "warn", - { - "callbacksLast": true, - "noSortAlphabetically": true, - "reservedFirst": true, - "shorthandFirst": true - } - ], - "react/jsx-uses-react": "off", - "react/no-unescaped-entities": "off", - "react/prop-types": "off", - "react/react-in-jsx-scope": "off", - "react/self-closing-comp": "warn", - "unused-imports/no-unused-imports": "warn", - "unused-imports/no-unused-vars": "off" - }, - "settings": { - "react": { - "version": "detect" - } - } -} From c7abc37671168db4441db502f4c11466cd4c329f Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Fri, 5 Jul 2024 11:01:12 +0200 Subject: [PATCH 009/411] chore: add prettier config files --- .prettierignore | 1 + .prettierrc.json | 9 +++++++++ 2 files changed, 10 insertions(+) create mode 100644 .prettierignore create mode 100644 .prettierrc.json diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000000..40b878db5b --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +node_modules/ \ No newline at end of file diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000000..b355961f7d --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,9 @@ +{ + "bracketSpacing": true, + "singleQuote": false, + "trailingComma": "all", + "tabWidth": 2, + "useTabs": false, + "semi": true, + "printWidth": 120 +} \ No newline at end of file From d4eabf2d7ea4116b2e85064cd255850454e20835 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Fri, 5 Jul 2024 11:01:34 +0200 Subject: [PATCH 010/411] chore: add nvmrc file --- .nvmrc | 1 + 1 file changed, 1 insertion(+) create mode 100644 .nvmrc diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000000..b009dfb9d9 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +lts/* From 6b028142eea1197c2e844cf59884eda12feb7195 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Fri, 5 Jul 2024 11:15:51 +0200 Subject: [PATCH 011/411] chore: add eslint-plugin-simple-import-sort as a dev dependencie --- package.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 158c6fc650..bf3e18e014 100644 --- a/package.json +++ b/package.json @@ -35,9 +35,10 @@ "typescript": "5.0.4" }, "devDependencies": { - "eslint-config-prettier": "^9.1.0" + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-simple-import-sort": "^12.1.1" }, - "name": "next-app-template", + "name": "prowler-next-app", "private": true, "scripts": { "build": "next build", From 67c6a12be4834a93f13c8b6309d89858dee615ca Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Fri, 5 Jul 2024 12:55:30 +0200 Subject: [PATCH 012/411] feat: add CI checks using GitHub actions --- .github/workflows/checks.yml | 28 ++++++++++++++++++++++++++++ package.json | 11 ++++++++--- 2 files changed, 36 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/checks.yml diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml new file mode 100644 index 0000000000..6e2b609c59 --- /dev/null +++ b/.github/workflows/checks.yml @@ -0,0 +1,28 @@ +name: CI - Code Quality and Health Check + +on: + pull_request: + branches: + - main + - develop + +jobs: + test-and-coverage: + runs-on: ubuntu-latest + strategy: + matrix: + os: [ubuntu-latest] + node-version: [20.x] + steps: + - name: Checkout repository + uses: actions/checkout@v3 + with: + persist-credentials: false + - name: Setup Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + - name: Install dependencies + run: npm install + - name: Run Healthcheck + run: npm healthcheck diff --git a/package.json b/package.json index bf3e18e014..b9cda2335c 100644 --- a/package.json +++ b/package.json @@ -41,10 +41,15 @@ "name": "prowler-next-app", "private": true, "scripts": { - "build": "next build", "dev": "next dev", - "lint": "eslint . --ext .ts,.tsx -c .eslintrc.json --fix", - "start": "next start" + "build": "next build", + "start": "next start", + "typecheck": "tsc", + "healthcheck": "npm run typecheck && npm run lint:check", + "lint:check": "./node_modules/.bin/eslint ./app", + "lint:fix": "eslint . --ext .ts,.tsx -c .eslintrc.json --fix", + "format:check": "./node_modules/.bin/prettier --check ./app", + "format:write": "./node_modules/.bin/prettier --config .prettierrc.json --write ./app" }, "version": "0.0.1" } From 0664032ef72133103f85a700515e7164189176b4 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Fri, 5 Jul 2024 13:06:36 +0200 Subject: [PATCH 013/411] feat: add CI checks using GitHub actions --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b9cda2335c..611c3b9137 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "typecheck": "tsc", "healthcheck": "npm run typecheck && npm run lint:check", "lint:check": "./node_modules/.bin/eslint ./app", - "lint:fix": "eslint . --ext .ts,.tsx -c .eslintrc.json --fix", + "lint:fix": "eslint . --ext .ts,.tsx -c .eslintrc.cjs --fix", "format:check": "./node_modules/.bin/prettier --check ./app", "format:write": "./node_modules/.bin/prettier --config .prettierrc.json --write ./app" }, From 8caae5996e0f8ed494097c6ba38713c5007eb0de Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Fri, 5 Jul 2024 13:44:17 +0200 Subject: [PATCH 014/411] chore: remove develop branch, we'll use just main --- .github/workflows/checks.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 6e2b609c59..af9eee32fd 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -4,7 +4,6 @@ on: pull_request: branches: - main - - develop jobs: test-and-coverage: From 01a0d07151bf12cad45905f1ba717de6441363e4 Mon Sep 17 00:00:00 2001 From: Sophia Dao Date: Sun, 7 Jul 2024 09:50:48 -0500 Subject: [PATCH 015/411] chore: clean-up repo --- Dockerfile | 11 - README.md | 38 ---- be_poc/api/__init__.py | 0 be_poc/api/admin.py | 6 - be_poc/api/apps.py | 6 - be_poc/api/fixtures/dummy_accounts.json | 203 ------------------ be_poc/api/migrations/0001_initial.py | 65 ------ ..._groups_cloudaccount_resources_and_more.py | 54 ----- ...003_alter_audit_aws_account_id_and_more.py | 24 --- ...move_account_billing_cancel_at_and_more.py | 97 --------- ...oudaccount_alias_cloudaccount_connected.py | 23 -- be_poc/api/migrations/__init__.py | 0 be_poc/api/models.py | 57 ----- be_poc/api/serializers.py | 31 --- be_poc/api/tests.py | 3 - be_poc/api/urls_v1.py | 8 - be_poc/api/views.py | 27 --- be_poc/be_poc/__init__.py | 0 be_poc/be_poc/asgi.py | 7 - be_poc/be_poc/settings.py | 119 ---------- be_poc/be_poc/urls.py | 7 - be_poc/be_poc/wsgi.py | 7 - be_poc/manage.py | 22 -- docker-compose.yaml | 7 - 24 files changed, 822 deletions(-) delete mode 100644 Dockerfile delete mode 100644 be_poc/api/__init__.py delete mode 100644 be_poc/api/admin.py delete mode 100644 be_poc/api/apps.py delete mode 100644 be_poc/api/fixtures/dummy_accounts.json delete mode 100644 be_poc/api/migrations/0001_initial.py delete mode 100644 be_poc/api/migrations/0002_cloudaccount_groups_cloudaccount_resources_and_more.py delete mode 100644 be_poc/api/migrations/0003_alter_audit_aws_account_id_and_more.py delete mode 100644 be_poc/api/migrations/0004_remove_account_billing_cancel_at_and_more.py delete mode 100644 be_poc/api/migrations/0005_cloudaccount_alias_cloudaccount_connected.py delete mode 100644 be_poc/api/migrations/__init__.py delete mode 100644 be_poc/api/models.py delete mode 100644 be_poc/api/serializers.py delete mode 100644 be_poc/api/tests.py delete mode 100644 be_poc/api/urls_v1.py delete mode 100644 be_poc/api/views.py delete mode 100644 be_poc/be_poc/__init__.py delete mode 100644 be_poc/be_poc/asgi.py delete mode 100644 be_poc/be_poc/settings.py delete mode 100644 be_poc/be_poc/urls.py delete mode 100644 be_poc/be_poc/wsgi.py delete mode 100755 be_poc/manage.py delete mode 100644 docker-compose.yaml diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 2b280ad451..0000000000 --- a/Dockerfile +++ /dev/null @@ -1,11 +0,0 @@ -FROM python:3.11.9-slim - -COPY requirements.txt /requirements.txt -COPY be_poc /be_poc - -RUN python -m pip install -r /requirements.txt -WORKDIR /be_poc -RUN python manage.py migrate && \ - python manage.py loaddata dummy_accounts - -ENTRYPOINT ["python", "manage.py", "runserver", "0.0.0.0:8000"] diff --git a/README.md b/README.md index 1843bf36d7..08df0a9c7c 100644 --- a/README.md +++ b/README.md @@ -51,41 +51,3 @@ After modifying the `.npmrc` file, you need to run `pnpm install` again to ensur ## License Licensed under the [MIT license](https://github.com/nextui-org/next-app-template/blob/main/LICENSE). - -# Prowler Django REST API (PoC) - -## Requirements - -- Have `docker` and `docker compose` installed. - -## How to run the REST API - -### Build the service image - -``` -docker compose build django-be-poc -``` - -### Start the service - -``` -docker compose up django-be-poc -``` - -## API - -The API will be accessible through HTTP, port `8080`. For instance, `http://localhost:8080/api/v1/`. - -### Implemented endpoints for the PoC: - -``` -/api/v1/providers/{provider_id}/accounts (the only available provider is 'aws') -``` - -### Expected response with `curl` - -```shellsession -curl http://localhost:8080/api/v1/providers/aws/accounts - -[{"id":1,"type":"Cloudy","enable":true,"provider_id":"aws","provider_data":{"test":"test value"},"inserted_at":"2024-06-24T10:20:18.309000Z","updated_at":"2024-06-24T10:20:18.309000Z","connected":true,"last_checked_at":"2024-06-24T10:20:04Z","alias":"dummy_alias","scanner_configuration":{"full_scan":true},"account_id":1}]% -``` diff --git a/be_poc/api/__init__.py b/be_poc/api/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/be_poc/api/admin.py b/be_poc/api/admin.py deleted file mode 100644 index 1132d5e64b..0000000000 --- a/be_poc/api/admin.py +++ /dev/null @@ -1,6 +0,0 @@ -from django.contrib import admin -from .models import Account, CloudAccount, Audit - -admin.site.register(Account) -admin.site.register(CloudAccount) -admin.site.register(Audit) diff --git a/be_poc/api/apps.py b/be_poc/api/apps.py deleted file mode 100644 index 66656fd29b..0000000000 --- a/be_poc/api/apps.py +++ /dev/null @@ -1,6 +0,0 @@ -from django.apps import AppConfig - - -class ApiConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'api' diff --git a/be_poc/api/fixtures/dummy_accounts.json b/be_poc/api/fixtures/dummy_accounts.json deleted file mode 100644 index bc4b04401d..0000000000 --- a/be_poc/api/fixtures/dummy_accounts.json +++ /dev/null @@ -1,203 +0,0 @@ -[ - { - "model": "api.account", - "pk": "c0bbdcd2-c69e-4bdf-bea7-8db6524fd205", - "fields": { - "name": "prod", - "inserted_at": "2024-06-25T09:03:20.273Z", - "updated_at": "2024-06-25T09:03:20.273Z", - "aws_account_id": "232136659152", - "scan_window_start_at": "2024-06-29T09:03:17Z" - } - }, - { - "model": "api.account", - "pk": "cf51b7a0-687e-4a2a-bf3d-14f98dcb4c3f", - "fields": { - "name": "platform", - "inserted_at": "2024-06-25T09:03:08.847Z", - "updated_at": "2024-06-25T09:03:08.847Z", - "aws_account_id": "714274078102", - "scan_window_start_at": "2024-06-28T09:03:08Z" - } - }, - { - "model": "api.account", - "pk": "cf9d2400-9a10-4c7b-a3be-6215c41b619e", - "fields": { - "name": "dev", - "inserted_at": "2024-06-25T09:02:34.188Z", - "updated_at": "2024-06-25T09:02:34.188Z", - "aws_account_id": "106908755756", - "scan_window_start_at": "2024-06-29T09:02:27Z" - } - }, - { - "model": "api.account", - "pk": "dbc452bd-b51c-4199-8b42-c1bc8164d45d", - "fields": { - "name": "marketplace", - "inserted_at": "2024-06-25T09:02:58.412Z", - "updated_at": "2024-06-25T09:02:58.412Z", - "aws_account_id": "879304169158", - "scan_window_start_at": "2024-06-27T09:02:57Z" - } - }, - { - "model": "api.account", - "pk": "fabc5b58-901b-4ba6-9901-4b2349acf492", - "fields": { - "name": "management", - "inserted_at": "2024-06-25T09:02:46.950Z", - "updated_at": "2024-06-25T09:02:46.950Z", - "aws_account_id": "741399645537", - "scan_window_start_at": "2024-06-27T09:02:46Z" - } - }, - { - "model": "api.cloudaccount", - "pk": "1f1dd221-6d49-4413-a8b7-e10ca4b0660a", - "fields": { - "account_id": "cf9d2400-9a10-4c7b-a3be-6215c41b619e", - "type": "test", - "groups": [ - "Operation" - ], - "resources": 644, - "enable": true, - "provider_id": "aws", - "inserted_at": "2024-06-25T09:03:57.233Z", - "updated_at": "2024-06-25T09:03:57.233Z", - "alias": "dev", - "connected": true - } - }, - { - "model": "api.cloudaccount", - "pk": "4997b2eb-a9f9-4d12-800b-1a6394728510", - "fields": { - "account_id": "c0bbdcd2-c69e-4bdf-bea7-8db6524fd205", - "type": "test", - "groups": [ - "Production" - ], - "resources": 564, - "enable": true, - "provider_id": "aws", - "inserted_at": "2024-06-25T09:04:56.941Z", - "updated_at": "2024-06-25T09:04:56.941Z", - "alias": "prod", - "connected": true - } - }, - { - "model": "api.cloudaccount", - "pk": "668bb77d-cfb4-4f4a-8c51-45beb16af4e9", - "fields": { - "account_id": "dbc452bd-b51c-4199-8b42-c1bc8164d45d", - "type": "test", - "groups": [ - "Production" - ], - "resources": 148, - "enable": true, - "provider_id": "aws", - "inserted_at": "2024-06-25T09:04:31.753Z", - "updated_at": "2024-06-25T09:04:31.753Z", - "alias": "marketplace", - "connected": true - } - }, - { - "model": "api.cloudaccount", - "pk": "a50d3d9d-ac2d-47d1-ac6c-29c8d304072e", - "fields": { - "account_id": "fabc5b58-901b-4ba6-9901-4b2349acf492", - "type": "test", - "groups": [ - "Production", - "Operation" - ], - "resources": 356, - "enable": true, - "provider_id": "aws", - "inserted_at": "2024-06-25T09:04:14.567Z", - "updated_at": "2024-06-25T09:04:14.567Z", - "alias": "management", - "connected": true - } - }, - { - "model": "api.cloudaccount", - "pk": "d71c8ac6-074d-45a3-a7a7-0595ab5440bc", - "fields": { - "account_id": "cf51b7a0-687e-4a2a-bf3d-14f98dcb4c3f", - "type": "test", - "groups": [ - "Production" - ], - "resources": 112, - "enable": true, - "provider_id": "aws", - "inserted_at": "2024-06-25T09:04:44.431Z", - "updated_at": "2024-06-25T09:04:44.431Z", - "alias": "platform", - "connected": true - } - }, - { - "model": "api.audit", - "pk": "0f712e8e-b419-40b4-9d85-65ef723fc1b9", - "fields": { - "aws_account_id": "fabc5b58-901b-4ba6-9901-4b2349acf492", - "audit_complete": true, - "inserted_at": "2024-06-25T09:05:37.352Z", - "updated_at": "2024-06-25T09:05:37.352Z", - "audit_duration": "00:06:30" - } - }, - { - "model": "api.audit", - "pk": "1bf18315-34e6-4912-8e8e-3e4650e4653e", - "fields": { - "aws_account_id": "dbc452bd-b51c-4199-8b42-c1bc8164d45d", - "audit_complete": true, - "inserted_at": "2024-06-25T09:05:43.601Z", - "updated_at": "2024-06-25T09:05:43.601Z", - "audit_duration": "00:08:00" - } - }, - { - "model": "api.audit", - "pk": "48605239-58ee-4dbf-8a46-3ef42b270a19", - "fields": { - "aws_account_id": "cf9d2400-9a10-4c7b-a3be-6215c41b619e", - "audit_complete": true, - "inserted_at": "2024-06-25T09:05:29.533Z", - "updated_at": "2024-06-25T09:05:29.533Z", - "audit_duration": "00:06:12" - } - }, - { - "model": "api.audit", - "pk": "955c1a4f-496a-4df5-96c8-578280b7a914", - "fields": { - "aws_account_id": "cf51b7a0-687e-4a2a-bf3d-14f98dcb4c3f", - "audit_complete": true, - "inserted_at": "2024-06-25T09:05:49.405Z", - "updated_at": "2024-06-25T09:05:49.405Z", - "audit_duration": "00:06:40" - } - }, - { - "model": "api.audit", - "pk": "ccbedd87-d64e-42dd-86d0-15ac60ade56d", - "fields": { - "aws_account_id": "c0bbdcd2-c69e-4bdf-bea7-8db6524fd205", - "audit_complete": true, - "inserted_at": "2024-06-25T09:06:00.463Z", - "updated_at": "2024-06-25T09:06:00.463Z", - "audit_duration": "00:07:12" - } - } -] diff --git a/be_poc/api/migrations/0001_initial.py b/be_poc/api/migrations/0001_initial.py deleted file mode 100644 index 5960d1d738..0000000000 --- a/be_poc/api/migrations/0001_initial.py +++ /dev/null @@ -1,65 +0,0 @@ -# Generated by Django 5.0.6 on 2024-06-24 10:11 - -import django.db.models.deletion -from django.db import migrations, models - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ] - - operations = [ - migrations.CreateModel( - name='Account', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=255)), - ('token', models.CharField(max_length=255)), - ('inserted_at', models.DateTimeField(auto_now_add=True)), - ('updated_at', models.DateTimeField(auto_now=True)), - ('card_brand', models.CharField(max_length=255)), - ('card_exp_month', models.IntegerField()), - ('card_exp_year', models.IntegerField()), - ('card_last4', models.CharField(max_length=4)), - ('onboarding_required', models.BooleanField()), - ('onboarding_step', models.CharField(max_length=255)), - ('onboarding_completed_at', models.DateTimeField()), - ('aws_account_id', models.CharField(max_length=255)), - ('off_boarded', models.BooleanField()), - ('billing_type', models.CharField(max_length=255)), - ('billing_cancel_at', models.DateTimeField()), - ('billing_current_period_end_at', models.DateTimeField()), - ('billing_free_resource_count', models.IntegerField()), - ('grafana_org_name', models.CharField(max_length=255)), - ('billing_email', models.CharField(max_length=255)), - ('scan_window_start_at', models.DateTimeField()), - ('company_name', models.CharField(max_length=255)), - ], - options={ - 'db_table': 'accounts', - }, - ), - migrations.CreateModel( - name='CloudAccount', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('type', models.CharField(max_length=255)), - ('enable', models.BooleanField()), - ('provider_id', models.CharField(max_length=255)), - ('provider_data', models.JSONField()), - ('inserted_at', models.DateTimeField(auto_now_add=True)), - ('updated_at', models.DateTimeField(auto_now=True)), - ('connected', models.BooleanField()), - ('last_checked_at', models.DateTimeField()), - ('alias', models.CharField(max_length=255)), - ('scanner_configuration', models.JSONField()), - ('account_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.account')), - ], - options={ - 'db_table': 'cloud_accounts', - }, - ), - ] diff --git a/be_poc/api/migrations/0002_cloudaccount_groups_cloudaccount_resources_and_more.py b/be_poc/api/migrations/0002_cloudaccount_groups_cloudaccount_resources_and_more.py deleted file mode 100644 index 20586a6a14..0000000000 --- a/be_poc/api/migrations/0002_cloudaccount_groups_cloudaccount_resources_and_more.py +++ /dev/null @@ -1,54 +0,0 @@ -# Generated by Django 5.0.6 on 2024-06-25 08:56 - -import django.db.models.deletion -import uuid -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('api', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='cloudaccount', - name='groups', - field=models.JSONField(default=list), - ), - migrations.AddField( - model_name='cloudaccount', - name='resources', - field=models.IntegerField(default=0), - ), - migrations.AlterField( - model_name='account', - name='id', - field=models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False), - ), - migrations.AlterField( - model_name='cloudaccount', - name='account_id', - field=models.ForeignKey(db_column='account_id', on_delete=django.db.models.deletion.CASCADE, to='api.account', unique=True), - ), - migrations.AlterField( - model_name='cloudaccount', - name='id', - field=models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False), - ), - migrations.CreateModel( - name='Audit', - fields=[ - ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), - ('audit_complete', models.BooleanField(default=True)), - ('inserted_at', models.DateTimeField(auto_now_add=True)), - ('updated_at', models.DateTimeField(auto_now=True)), - ('audit_duration', models.DurationField()), - ('aws_account_id', models.ForeignKey(db_column='aws_account_id', on_delete=django.db.models.deletion.CASCADE, to='api.cloudaccount', to_field='account_id', unique=True)), - ], - options={ - 'db_table': 'audits', - }, - ), - ] diff --git a/be_poc/api/migrations/0003_alter_audit_aws_account_id_and_more.py b/be_poc/api/migrations/0003_alter_audit_aws_account_id_and_more.py deleted file mode 100644 index 971d1370b6..0000000000 --- a/be_poc/api/migrations/0003_alter_audit_aws_account_id_and_more.py +++ /dev/null @@ -1,24 +0,0 @@ -# Generated by Django 5.0.6 on 2024-06-25 08:57 - -import django.db.models.deletion -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('api', '0002_cloudaccount_groups_cloudaccount_resources_and_more'), - ] - - operations = [ - migrations.AlterField( - model_name='audit', - name='aws_account_id', - field=models.OneToOneField(db_column='aws_account_id', on_delete=django.db.models.deletion.CASCADE, to='api.cloudaccount', to_field='account_id'), - ), - migrations.AlterField( - model_name='cloudaccount', - name='account_id', - field=models.OneToOneField(db_column='account_id', on_delete=django.db.models.deletion.CASCADE, to='api.account'), - ), - ] diff --git a/be_poc/api/migrations/0004_remove_account_billing_cancel_at_and_more.py b/be_poc/api/migrations/0004_remove_account_billing_cancel_at_and_more.py deleted file mode 100644 index 89ad02512d..0000000000 --- a/be_poc/api/migrations/0004_remove_account_billing_cancel_at_and_more.py +++ /dev/null @@ -1,97 +0,0 @@ -# Generated by Django 5.0.6 on 2024-06-25 09:01 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('api', '0003_alter_audit_aws_account_id_and_more'), - ] - - operations = [ - migrations.RemoveField( - model_name='account', - name='billing_cancel_at', - ), - migrations.RemoveField( - model_name='account', - name='billing_current_period_end_at', - ), - migrations.RemoveField( - model_name='account', - name='billing_email', - ), - migrations.RemoveField( - model_name='account', - name='billing_free_resource_count', - ), - migrations.RemoveField( - model_name='account', - name='billing_type', - ), - migrations.RemoveField( - model_name='account', - name='card_brand', - ), - migrations.RemoveField( - model_name='account', - name='card_exp_month', - ), - migrations.RemoveField( - model_name='account', - name='card_exp_year', - ), - migrations.RemoveField( - model_name='account', - name='card_last4', - ), - migrations.RemoveField( - model_name='account', - name='company_name', - ), - migrations.RemoveField( - model_name='account', - name='grafana_org_name', - ), - migrations.RemoveField( - model_name='account', - name='off_boarded', - ), - migrations.RemoveField( - model_name='account', - name='onboarding_completed_at', - ), - migrations.RemoveField( - model_name='account', - name='onboarding_required', - ), - migrations.RemoveField( - model_name='account', - name='onboarding_step', - ), - migrations.RemoveField( - model_name='account', - name='token', - ), - migrations.RemoveField( - model_name='cloudaccount', - name='alias', - ), - migrations.RemoveField( - model_name='cloudaccount', - name='connected', - ), - migrations.RemoveField( - model_name='cloudaccount', - name='last_checked_at', - ), - migrations.RemoveField( - model_name='cloudaccount', - name='provider_data', - ), - migrations.RemoveField( - model_name='cloudaccount', - name='scanner_configuration', - ), - ] diff --git a/be_poc/api/migrations/0005_cloudaccount_alias_cloudaccount_connected.py b/be_poc/api/migrations/0005_cloudaccount_alias_cloudaccount_connected.py deleted file mode 100644 index 9edec907e3..0000000000 --- a/be_poc/api/migrations/0005_cloudaccount_alias_cloudaccount_connected.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 4.2.13 on 2024-06-26 07:56 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('api', '0004_remove_account_billing_cancel_at_and_more'), - ] - - operations = [ - migrations.AddField( - model_name='cloudaccount', - name='alias', - field=models.CharField(max_length=255, null=True), - ), - migrations.AddField( - model_name='cloudaccount', - name='connected', - field=models.BooleanField(default=False), - ), - ] diff --git a/be_poc/api/migrations/__init__.py b/be_poc/api/migrations/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/be_poc/api/models.py b/be_poc/api/models.py deleted file mode 100644 index 067c24b0d5..0000000000 --- a/be_poc/api/models.py +++ /dev/null @@ -1,57 +0,0 @@ -import uuid - -from django.db import models - - -class Account(models.Model): - class Meta: - db_table = "accounts" - - id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) - name = models.CharField(max_length=255) - inserted_at = models.DateTimeField(auto_now_add=True) - updated_at = models.DateTimeField(auto_now=True) - aws_account_id = models.CharField(max_length=255) - scan_window_start_at = models.DateTimeField() - - def __str__(self): - return f"{self.name} - {self.id}" - - -class CloudAccount(models.Model): - class Meta: - db_table = "cloud_accounts" - - id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) - account_id = models.OneToOneField( - Account, on_delete=models.CASCADE, db_column="account_id", to_field="id" - ) - type = models.CharField(max_length=255) - groups = models.JSONField(default=list) - resources = models.IntegerField(default=0) - enable = models.BooleanField() - alias = models.CharField(max_length=255, null=True) - connected = models.BooleanField(default=False) - provider_id = models.CharField(max_length=255) - inserted_at = models.DateTimeField(auto_now_add=True) - updated_at = models.DateTimeField(auto_now=True) - - def __str__(self): - return f"{self.id} ({self.provider_id})" - - -class Audit(models.Model): - class Meta: - db_table = "audits" - - id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) - aws_account_id = models.OneToOneField( - CloudAccount, on_delete=models.CASCADE, db_column="aws_account_id", to_field="account_id" - ) - audit_complete = models.BooleanField(default=True) - inserted_at = models.DateTimeField(auto_now_add=True) - updated_at = models.DateTimeField(auto_now=True) - audit_duration = models.DurationField() - - def __str__(self): - return f"{self.id} - {self.aws_account_id}" diff --git a/be_poc/api/serializers.py b/be_poc/api/serializers.py deleted file mode 100644 index 171a69a73c..0000000000 --- a/be_poc/api/serializers.py +++ /dev/null @@ -1,31 +0,0 @@ -from rest_framework import serializers -from .models import Account, CloudAccount, Audit - - -class AccountSerializer(serializers.ModelSerializer): - class Meta: - model = Account - fields = "__all__" - - -class CloudAccountSerializer(serializers.ModelSerializer): - aws_account_id = serializers.SerializerMethodField() - - class Meta: - model = CloudAccount - fields = "__all__" - extra_fields = ["aws_account_id"] - - def get_aws_account_id(self, obj): - return obj.account_id.aws_account_id - - -class AuditSerializer(serializers.ModelSerializer): - account_id = serializers.SerializerMethodField() - - class Meta: - model = Audit - fields = ['id', 'audit_complete', 'inserted_at', 'updated_at', 'audit_duration', 'account_id'] - - def get_account_id(self, obj): - return obj.aws_account_id.account_id.id diff --git a/be_poc/api/tests.py b/be_poc/api/tests.py deleted file mode 100644 index 7ce503c2dd..0000000000 --- a/be_poc/api/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/be_poc/api/urls_v1.py b/be_poc/api/urls_v1.py deleted file mode 100644 index 9386c301dc..0000000000 --- a/be_poc/api/urls_v1.py +++ /dev/null @@ -1,8 +0,0 @@ -from django.urls import path - -from .views import AuditListView, CloudAccountListView - -urlpatterns = [ - path('providers//accounts', CloudAccountListView.as_view(), name='cloud-account-list'), - path('providers/aws/audits', AuditListView.as_view(), name='aws-audit-list'), -] diff --git a/be_poc/api/views.py b/be_poc/api/views.py deleted file mode 100644 index ebbddb1450..0000000000 --- a/be_poc/api/views.py +++ /dev/null @@ -1,27 +0,0 @@ -from rest_framework import generics -from .models import Account, CloudAccount, Audit -from .serializers import AccountSerializer, CloudAccountSerializer, AuditSerializer - - -class AccountListView(generics.ListAPIView): - queryset = Account.objects.all() - serializer_class = AccountSerializer - - -class CloudAccountListView(generics.ListAPIView): - serializer_class = CloudAccountSerializer - - def get_queryset(self): - provider_id = self.kwargs.get('provider_id') - return CloudAccount.objects.filter(provider_id=provider_id) - - -class AuditListView(generics.ListAPIView): - serializer_class = AuditSerializer - - def get_queryset(self): - queryset = Audit.objects.all() - account_id = self.request.query_params.get('account_id') - if account_id: - queryset = queryset.filter(aws_account_id__account_id=account_id) - return queryset diff --git a/be_poc/be_poc/__init__.py b/be_poc/be_poc/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/be_poc/be_poc/asgi.py b/be_poc/be_poc/asgi.py deleted file mode 100644 index 2b8704ac78..0000000000 --- a/be_poc/be_poc/asgi.py +++ /dev/null @@ -1,7 +0,0 @@ -import os - -from django.core.asgi import get_asgi_application - -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'be_poc.settings') - -application = get_asgi_application() diff --git a/be_poc/be_poc/settings.py b/be_poc/be_poc/settings.py deleted file mode 100644 index f6fb8e2fee..0000000000 --- a/be_poc/be_poc/settings.py +++ /dev/null @@ -1,119 +0,0 @@ -from pathlib import Path - -# Build paths inside the project like this: BASE_DIR / 'subdir'. -BASE_DIR = Path(__file__).resolve().parent.parent - - -# Quick-start development settings - unsuitable for production -# See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/ - -# SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = 'django-insecure-h72a3mgf$l$1uqnjf0bbbz9fbmuotlff7ya50+hlao)fga=3dp' - -# SECURITY WARNING: don't run with debug turned on in production! -DEBUG = True - -ALLOWED_HOSTS = ['localhost', 'steady-separately-marlin.ngrok-free.app'] - - -# Application definition - -INSTALLED_APPS = [ - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', - - 'rest_framework', - 'api', - 'corsheaders', -] - -MIDDLEWARE = [ - 'django.middleware.security.SecurityMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', - - 'corsheaders.middleware.CorsMiddleware' -] - -CORS_ALLOW_ALL_ORIGINS = True - -ROOT_URLCONF = 'be_poc.urls' - -TEMPLATES = [ - { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [], - 'APP_DIRS': True, - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', - ], - }, - }, -] - -WSGI_APPLICATION = 'be_poc.wsgi.application' - - -# Database -# https://docs.djangoproject.com/en/5.0/ref/settings/#databases - -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': BASE_DIR / 'db.sqlite3', - } -} - - -# Password validation -# https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators - -AUTH_PASSWORD_VALIDATORS = [ - { - 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', - }, - { - 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', - }, - { - 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', - }, - { - 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', - }, -] - - -# Internationalization -# https://docs.djangoproject.com/en/5.0/topics/i18n/ - -LANGUAGE_CODE = 'en-us' - -TIME_ZONE = 'UTC' - -USE_I18N = True - -USE_TZ = True - - -# Static files (CSS, JavaScript, Images) -# https://docs.djangoproject.com/en/5.0/howto/static-files/ - -STATIC_URL = 'static/' - -# Default primary key field type -# https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field - -DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' diff --git a/be_poc/be_poc/urls.py b/be_poc/be_poc/urls.py deleted file mode 100644 index 8c12f37f20..0000000000 --- a/be_poc/be_poc/urls.py +++ /dev/null @@ -1,7 +0,0 @@ -from django.contrib import admin -from django.urls import path, include - -urlpatterns = [ - path('admin/', admin.site.urls), - path('api/v1/', include('api.urls_v1')), -] diff --git a/be_poc/be_poc/wsgi.py b/be_poc/be_poc/wsgi.py deleted file mode 100644 index 0a66b5cc5e..0000000000 --- a/be_poc/be_poc/wsgi.py +++ /dev/null @@ -1,7 +0,0 @@ -import os - -from django.core.wsgi import get_wsgi_application - -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'be_poc.settings') - -application = get_wsgi_application() diff --git a/be_poc/manage.py b/be_poc/manage.py deleted file mode 100755 index b07263b7cb..0000000000 --- a/be_poc/manage.py +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env python -"""Django's command-line utility for administrative tasks.""" -import os -import sys - - -def main(): - """Run administrative tasks.""" - os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'be_poc.settings') - try: - from django.core.management import execute_from_command_line - except ImportError as exc: - raise ImportError( - "Couldn't import Django. Are you sure it's installed and " - "available on your PYTHONPATH environment variable? Did you " - "forget to activate a virtual environment?" - ) from exc - execute_from_command_line(sys.argv) - - -if __name__ == '__main__': - main() diff --git a/docker-compose.yaml b/docker-compose.yaml deleted file mode 100644 index 745691a46c..0000000000 --- a/docker-compose.yaml +++ /dev/null @@ -1,7 +0,0 @@ -services: - django-be-poc: - image: django-be-poc - build: - context: "./" - ports: - - "8080:8000" From 721aea945aaff53f17f06da216a9ecde190fed8c Mon Sep 17 00:00:00 2001 From: Sophia Dao Date: Sun, 7 Jul 2024 09:56:54 -0500 Subject: [PATCH 016/411] chore: clean up due to linter rules --- app/about/layout.tsx | 10 +--- app/blog/layout.tsx | 10 +--- app/clouds/layout.tsx | 6 +-- app/clouds/page.tsx | 53 ++++++++------------- app/docs/layout.tsx | 10 +--- app/error.tsx | 8 +--- app/layout.tsx | 28 ++++------- app/login/layout.tsx | 10 +--- app/login/page.tsx | 25 ++-------- app/page.tsx | 25 ++-------- app/pricing/layout.tsx | 10 +--- app/providers.tsx | 2 +- components/counter.tsx | 2 +- components/icons.tsx | 95 +++++-------------------------------- components/navbar.tsx | 12 ++--- components/primitives.ts | 10 +--- components/theme-switch.tsx | 34 ++++--------- 17 files changed, 78 insertions(+), 272 deletions(-) diff --git a/app/about/layout.tsx b/app/about/layout.tsx index 98956a52ad..cfdc03b801 100644 --- a/app/about/layout.tsx +++ b/app/about/layout.tsx @@ -1,13 +1,7 @@ -export default function AboutLayout({ - children, -}: { - children: React.ReactNode; -}) { +export default function AboutLayout({ children }: { children: React.ReactNode }) { return (
-
- {children} -
+
{children}
); } diff --git a/app/blog/layout.tsx b/app/blog/layout.tsx index 911d0bcf41..365caee773 100644 --- a/app/blog/layout.tsx +++ b/app/blog/layout.tsx @@ -1,13 +1,7 @@ -export default function BlogLayout({ - children, -}: { - children: React.ReactNode; -}) { +export default function BlogLayout({ children }: { children: React.ReactNode }) { return (
-
- {children} -
+
{children}
); } diff --git a/app/clouds/layout.tsx b/app/clouds/layout.tsx index d9ca1e80e4..8c8daab55d 100644 --- a/app/clouds/layout.tsx +++ b/app/clouds/layout.tsx @@ -1,8 +1,4 @@ -export default function CloudsLayout({ - children, -}: { - children: React.ReactNode; -}) { +export default function CloudsLayout({ children }: { children: React.ReactNode }) { return (
{children}
diff --git a/app/clouds/page.tsx b/app/clouds/page.tsx index a6f35d6e16..48d18551f8 100644 --- a/app/clouds/page.tsx +++ b/app/clouds/page.tsx @@ -1,43 +1,34 @@ "use client"; -import useSWR from "swr"; -import React from "react"; - +// import { PencilSquareIcon } from "@heroicons/react/24/solid"; +// import { TrashIcon } from "@heroicons/react/24/solid"; +// import { EyeIcon } from "@heroicons/react/24/solid"; import { + // Chip, + // getKeyValue, Table, - TableHeader, TableBody, - TableColumn, - TableRow, TableCell, - User, - Chip, - Tooltip, - getKeyValue, + TableColumn, + TableHeader, + TableRow, + // Tooltip, + // User, } from "@nextui-org/react"; -import { fetcher } from "@/utils/fetcher"; -import { title } from "@/components/primitives"; +import React from "react"; +import useSWR from "swr"; -import { PencilSquareIcon } from "@heroicons/react/24/solid"; -import { TrashIcon } from "@heroicons/react/24/solid"; -import { EyeIcon } from "@heroicons/react/24/solid"; +import { title } from "@/components/primitives"; +import { fetcher } from "@/utils/fetcher"; export default function CloudsPage() { - const getAccounts = useSWR( - `http://localhost:8080/api/v1/providers/aws/accounts`, - fetcher, - ); + const getAccounts = useSWR("http://localhost:8080/api/v1/providers/aws/accounts", fetcher); - const getAudits = useSWR( - `http://localhost:8080/api/v1/providers/aws/audits`, - fetcher, - ); + const getAudits = useSWR("http://localhost:8080/api/v1/providers/aws/audits", fetcher); // TODO FIX TYPE CHECKING - const getScanDetails = (account_id: Number, detail: String) => { - const scan = - getAudits.data && - getAudits.data.find((audit: any) => audit.account_id === account_id); + const getScanDetails = (account_id: number, detail: string) => { + const scan = getAudits.data && getAudits.data.find((audit: any) => audit.account_id === account_id); if (detail === "status") { return scan?.audit_complete && "Completed"; @@ -79,12 +70,8 @@ export default function CloudsPage() {

Cloud Accounts

- {getAccounts.error && ( - Failed to load - )} - {getAccounts.isLoading && ( - Loading - )} + {getAccounts.error && Failed to load} + {getAccounts.isLoading && Loading}

{getAccounts.data && ( diff --git a/app/docs/layout.tsx b/app/docs/layout.tsx index eaf63f3b99..e3d89f490a 100644 --- a/app/docs/layout.tsx +++ b/app/docs/layout.tsx @@ -1,13 +1,7 @@ -export default function DocsLayout({ - children, -}: { - children: React.ReactNode; -}) { +export default function DocsLayout({ children }: { children: React.ReactNode }) { return (
-
- {children} -
+
{children}
); } diff --git a/app/error.tsx b/app/error.tsx index 9ed5104e8a..388b7fcc7b 100644 --- a/app/error.tsx +++ b/app/error.tsx @@ -2,13 +2,7 @@ import { useEffect } from "react"; -export default function Error({ - error, - reset, -}: { - error: Error; - reset: () => void; -}) { +export default function Error({ error, reset }: { error: Error; reset: () => void }) { useEffect(() => { // Log the error to an error reporting service /* eslint-disable no-console */ diff --git a/app/layout.tsx b/app/layout.tsx index d81ea47ba9..39517f2bf1 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,10 +1,11 @@ -import { Metadata, Viewport } from "next"; -import clsx from "clsx"; - import "@/styles/globals.css"; -import { siteConfig } from "@/config/site"; -import { fontSans } from "@/config/fonts"; + +import clsx from "clsx"; +import { Metadata, Viewport } from "next"; + import { Navbar } from "@/components/navbar"; +import { fontSans } from "@/config/fonts"; +import { siteConfig } from "@/config/site"; import { Providers } from "./providers"; @@ -26,26 +27,15 @@ export const viewport: Viewport = { ], }; -export default function RootLayout({ - children, -}: { - children: React.ReactNode; -}) { +export default function RootLayout({ children }: { children: React.ReactNode }) { return ( - +
-
- {children} -
+
{children}
diff --git a/app/login/layout.tsx b/app/login/layout.tsx index d3a4a58ea4..561d6bcc49 100644 --- a/app/login/layout.tsx +++ b/app/login/layout.tsx @@ -1,13 +1,7 @@ -export default function LoginLayout({ - children, -}: { - children: React.ReactNode; -}) { +export default function LoginLayout({ children }: { children: React.ReactNode }) { return (
-
- {children} -
+
{children}
); } diff --git a/app/login/page.tsx b/app/login/page.tsx index 113f81418e..c348917daf 100644 --- a/app/login/page.tsx +++ b/app/login/page.tsx @@ -1,17 +1,9 @@ "use client"; -import React from "react"; -import { - Card, - CardHeader, - CardBody, - CardFooter, - Input, - Link, - Button, -} from "@nextui-org/react"; import { EyeIcon } from "@heroicons/react/24/solid"; import { EyeSlashIcon } from "@heroicons/react/24/solid"; +import { Button, Card, CardBody, CardFooter, CardHeader, Input, Link } from "@nextui-org/react"; +import React from "react"; export default function LoginPage() { const [isVisible, setIsVisible] = React.useState(false); @@ -26,22 +18,13 @@ export default function LoginPage() {

Login

- + +
diff --git a/app/docs/layout.tsx b/app/docs/layout.tsx index e3d89f490a..eaf63f3b99 100644 --- a/app/docs/layout.tsx +++ b/app/docs/layout.tsx @@ -1,7 +1,13 @@ -export default function DocsLayout({ children }: { children: React.ReactNode }) { +export default function DocsLayout({ + children, +}: { + children: React.ReactNode; +}) { return (
-
{children}
+
+ {children} +
); } diff --git a/app/error.tsx b/app/error.tsx index 388b7fcc7b..9ed5104e8a 100644 --- a/app/error.tsx +++ b/app/error.tsx @@ -2,7 +2,13 @@ import { useEffect } from "react"; -export default function Error({ error, reset }: { error: Error; reset: () => void }) { +export default function Error({ + error, + reset, +}: { + error: Error; + reset: () => void; +}) { useEffect(() => { // Log the error to an error reporting service /* eslint-disable no-console */ diff --git a/app/layout.tsx b/app/layout.tsx index 39517f2bf1..275ebe292b 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,9 +1,9 @@ import "@/styles/globals.css"; +import { Navbar } from "@nextui-org/react"; import clsx from "clsx"; import { Metadata, Viewport } from "next"; -import { Navbar } from "@/components/navbar"; import { fontSans } from "@/config/fonts"; import { siteConfig } from "@/config/site"; @@ -27,15 +27,26 @@ export const viewport: Viewport = { ], }; -export default function RootLayout({ children }: { children: React.ReactNode }) { +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { return ( - +
-
{children}
+
+ {children} +
diff --git a/app/login/layout.tsx b/app/login/layout.tsx index 561d6bcc49..d3a4a58ea4 100644 --- a/app/login/layout.tsx +++ b/app/login/layout.tsx @@ -1,7 +1,13 @@ -export default function LoginLayout({ children }: { children: React.ReactNode }) { +export default function LoginLayout({ + children, +}: { + children: React.ReactNode; +}) { return (
-
{children}
+
+ {children} +
); } diff --git a/app/login/page.tsx b/app/login/page.tsx index c348917daf..f07535d803 100644 --- a/app/login/page.tsx +++ b/app/login/page.tsx @@ -2,7 +2,15 @@ import { EyeIcon } from "@heroicons/react/24/solid"; import { EyeSlashIcon } from "@heroicons/react/24/solid"; -import { Button, Card, CardBody, CardFooter, CardHeader, Input, Link } from "@nextui-org/react"; +import { + Button, + Card, + CardBody, + CardFooter, + CardHeader, + Input, + Link, +} from "@nextui-org/react"; import React from "react"; export default function LoginPage() { @@ -18,13 +26,22 @@ export default function LoginPage() {

Login

- + + - ); -}; diff --git a/components/icons.tsx b/components/temp_Icons.tsx similarity index 100% rename from components/icons.tsx rename to components/temp_Icons.tsx diff --git a/components/navbar.tsx b/components/temp_Navbar.tsx similarity index 100% rename from components/navbar.tsx rename to components/temp_Navbar.tsx From 4d43a6bdd6349498e82705a8e3d6df5cdefcc5e3 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Tue, 9 Jul 2024 13:43:49 +0200 Subject: [PATCH 026/411] chore: rename temp_files.tsx to fix case sensitivity issue on GitHub --- components/{temp_Icons.tsx => Icons.tsx} | 0 components/{temp_Navbar.tsx => Navbar.tsx} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename components/{temp_Icons.tsx => Icons.tsx} (100%) rename components/{temp_Navbar.tsx => Navbar.tsx} (100%) diff --git a/components/temp_Icons.tsx b/components/Icons.tsx similarity index 100% rename from components/temp_Icons.tsx rename to components/Icons.tsx diff --git a/components/temp_Navbar.tsx b/components/Navbar.tsx similarity index 100% rename from components/temp_Navbar.tsx rename to components/Navbar.tsx From ee640da9e79b689fb1cc45ed0045c90c22f7cf9f Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Tue, 9 Jul 2024 17:11:45 +0200 Subject: [PATCH 027/411] Add eslint-plugin-security and pre-commit hooks. (#5) * feat: add eslint-plugin-security * chore: relocate devDependencies to the appropriate section in package.json * feat: add husky library for pre-commit hooks * feat: add husky library for pre-commit hooks * chore: improve prettierrc config --- .eslintrc.cjs | 1 + .husky/pre-commit | 1 + .prettierrc.json | 4 ++-- .vscode/settings.json | 10 +++++++++- package.json | 30 +++++++++++++++++------------- 5 files changed, 30 insertions(+), 16 deletions(-) create mode 100644 .husky/pre-commit diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 1843e62bcf..902ee59d19 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -9,6 +9,7 @@ module.exports = { "eslint:recommended", "plugin:@typescript-eslint/eslint-recommended", "plugin:@typescript-eslint/recommended", + "plugin:security/recommended-legacy", "prettier", ], parserOptions: { diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 0000000000..129b71298f --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1 @@ +npm run healthcheck diff --git a/.prettierrc.json b/.prettierrc.json index b355961f7d..15d19325bb 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -5,5 +5,5 @@ "tabWidth": 2, "useTabs": false, "semi": true, - "printWidth": 120 -} \ No newline at end of file + "printWidth": 80 +} diff --git a/.vscode/settings.json b/.vscode/settings.json index 25fa6215fd..104c678edb 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,11 @@ { - "typescript.tsdk": "node_modules/typescript/lib" + "editor.formatOnSave": true, + "editor.defaultFormatter": "esbenp.prettier-vscode", + "prettier.printWidth": 80, + "prettier.tabWidth": 2, + "prettier.useTabs": false, + "prettier.singleQuote": false, + "prettier.trailingComma": "all", + "prettier.semi": true, + "prettier.bracketSpacing": true } diff --git a/package.json b/package.json index 611c3b9137..c7ba81e435 100644 --- a/package.json +++ b/package.json @@ -6,38 +6,41 @@ "@nextui-org/theme": "2.2.5", "@react-aria/ssr": "3.9.4", "@react-aria/visually-hidden": "3.8.12", + "clsx": "2.1.1", + "framer-motion": "~11.1.1", + "intl-messageformat": "^10.5.0", + "next": "14.2.3", + "next-themes": "^0.2.1", + "react": "18.3.1", + "react-dom": "18.3.1", + "swr": "^2.2.5" + }, + "devDependencies": { "@types/node": "20.5.7", "@types/react": "18.3.3", "@types/react-dom": "18.3.0", "@typescript-eslint/eslint-plugin": "^7.10.0", "@typescript-eslint/parser": "^7.10.0", "autoprefixer": "10.4.19", - "clsx": "2.1.1", "eslint": "^8.56.0", "eslint-config-next": "14.2.1", + "eslint-config-prettier": "^9.1.0", "eslint-plugin-import": "^2.26.0", "eslint-plugin-jsx-a11y": "^6.4.1", "eslint-plugin-node": "^11.1.0", "eslint-plugin-prettier": "^5.1.3", "eslint-plugin-react": "^7.23.2", "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-security": "^3.0.1", + "eslint-plugin-simple-import-sort": "^12.1.1", "eslint-plugin-unused-imports": "^3.2.0", - "framer-motion": "~11.1.1", - "intl-messageformat": "^10.5.0", - "next": "14.2.3", - "next-themes": "^0.2.1", + "husky": "^9.0.11", + "lint-staged": "^15.2.7", "postcss": "8.4.38", - "react": "18.3.1", - "react-dom": "18.3.1", - "swr": "^2.2.5", "tailwind-variants": "0.1.20", "tailwindcss": "3.4.3", "typescript": "5.0.4" }, - "devDependencies": { - "eslint-config-prettier": "^9.1.0", - "eslint-plugin-simple-import-sort": "^12.1.1" - }, "name": "prowler-next-app", "private": true, "scripts": { @@ -49,7 +52,8 @@ "lint:check": "./node_modules/.bin/eslint ./app", "lint:fix": "eslint . --ext .ts,.tsx -c .eslintrc.cjs --fix", "format:check": "./node_modules/.bin/prettier --check ./app", - "format:write": "./node_modules/.bin/prettier --config .prettierrc.json --write ./app" + "format:write": "./node_modules/.bin/prettier --config .prettierrc.json --write ./app", + "prepare": "husky" }, "version": "0.0.1" } From 86df1fd98e844e306cd4e524f9140c9ad256118b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Jul 2024 22:08:57 +0000 Subject: [PATCH 028/411] build(deps): bump django from 5.0.6 to 5.0.7 Bumps [django](https://github.com/django/django) from 5.0.6 to 5.0.7. - [Commits](https://github.com/django/django/compare/5.0.6...5.0.7) --- updated-dependencies: - dependency-name: django dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index e78d72ead8..e5e44befb9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ -Django==5.0.6 +Django==5.0.7 djangorestframework==3.15.2 django-cors-headers==4.3.1 From 746b427943851a6bfdaaeafe6c0e65d412e28909 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Thu, 11 Jul 2024 10:31:06 +0200 Subject: [PATCH 029/411] feat: set basic sidebar and the main layout --- app/layout.tsx | 13 +- app/page.tsx | 224 +++++++++---- components/acme.tsx | 25 ++ components/sidebar.tsx | 0 components/sidebar/Sidebar.tsx | 327 ++++++++++++++++++ components/sidebar/SidebarItems.tsx | 503 ++++++++++++++++++++++++++++ components/sidebar/SidebarWrap.tsx | 191 +++++++++++ components/sidebar/TeamAvatar.tsx | 38 +++ components/sidebar/UserAvatar.tsx | 35 ++ package.json | 4 +- tailwind.config.js | 13 +- utils/cn.ts | 22 ++ 12 files changed, 1327 insertions(+), 68 deletions(-) create mode 100644 components/acme.tsx delete mode 100644 components/sidebar.tsx create mode 100644 components/sidebar/Sidebar.tsx create mode 100644 components/sidebar/SidebarItems.tsx create mode 100644 components/sidebar/SidebarWrap.tsx create mode 100644 components/sidebar/TeamAvatar.tsx create mode 100644 components/sidebar/UserAvatar.tsx create mode 100644 utils/cn.ts diff --git a/app/layout.tsx b/app/layout.tsx index 275ebe292b..074a962755 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,12 +1,13 @@ import "@/styles/globals.css"; -import { Navbar } from "@nextui-org/react"; import clsx from "clsx"; import { Metadata, Viewport } from "next"; +import React from "react"; import { fontSans } from "@/config/fonts"; import { siteConfig } from "@/config/site"; +import { SidebarWrap } from "../components/sidebar/SidebarWrap"; import { Providers } from "./providers"; export const metadata: Metadata = { @@ -42,11 +43,11 @@ export default function RootLayout({ )} > -
- -
- {children} -
+
+
+ +
{children}
+
diff --git a/app/page.tsx b/app/page.tsx index ceb0e6a542..268ac3a2ea 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,65 +1,177 @@ "use client"; -import { EyeIcon, EyeSlashIcon } from "@heroicons/react/24/solid"; -import { - Button, - Card, - CardBody, - CardFooter, - CardHeader, - Input, - Link, -} from "@nextui-org/react"; +import { Icon } from "@iconify/react"; +import { Button } from "@nextui-org/react"; import React from "react"; export default function Home() { - const [isVisible, setIsVisible] = React.useState(false); - - const toggleVisibility = () => setIsVisible(!isVisible); - return ( -
-
- - -

Login

-
- - +
+
+ - } - type={isVisible ? "text" : "password"} - className="max-w-xs" - /> - - - - - -

This is a page with "use client", useState

-
-
+ +

Overview

+ +
+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+

hi hi from the overview page

+
+ + ); } diff --git a/components/acme.tsx b/components/acme.tsx new file mode 100644 index 0000000000..9478723fa5 --- /dev/null +++ b/components/acme.tsx @@ -0,0 +1,25 @@ +import React from "react"; + +import { IconSvgProps } from "../types/index"; + +export const AcmeIcon: React.FC = ({ + size = 32, + width, + height, + ...props +}) => ( + + + +); diff --git a/components/sidebar.tsx b/components/sidebar.tsx deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/components/sidebar/Sidebar.tsx b/components/sidebar/Sidebar.tsx new file mode 100644 index 0000000000..49df2deac7 --- /dev/null +++ b/components/sidebar/Sidebar.tsx @@ -0,0 +1,327 @@ +"use client"; + +import { Icon } from "@iconify/react"; +import { + Accordion, + AccordionItem, + type ListboxProps, + type ListboxSectionProps, + type Selection, +} from "@nextui-org/react"; +import { + Listbox, + ListboxItem, + ListboxSection, + Tooltip, +} from "@nextui-org/react"; +import React from "react"; + +import { cn } from "@/utils/cn"; + +export enum SidebarItemType { + Nest = "nest", +} + +export type SidebarItem = { + key: string; + title: string; + icon?: string; + href?: string; + type?: SidebarItemType.Nest; + startContent?: React.ReactNode; + endContent?: React.ReactNode; + items?: SidebarItem[]; + className?: string; +}; + +export type SidebarProps = Omit, "children"> & { + items: SidebarItem[]; + isCompact?: boolean; + hideEndContent?: boolean; + iconClassName?: string; + sectionClasses?: ListboxSectionProps["classNames"]; + classNames?: ListboxProps["classNames"]; + defaultSelectedKey: string; + onSelect?: (key: string) => void; +}; + +const Sidebar = React.forwardRef( + ( + { + items, + isCompact, + defaultSelectedKey, + onSelect, + hideEndContent, + sectionClasses: sectionClassesProp = {}, + itemClasses: itemClassesProp = {}, + iconClassName, + classNames, + className, + ...props + }, + ref, + ) => { + const [selected, setSelected] = + React.useState(defaultSelectedKey); + + const sectionClasses = { + ...sectionClassesProp, + base: cn(sectionClassesProp?.base, "w-full", { + "p-0 max-w-[44px]": isCompact, + }), + group: cn(sectionClassesProp?.group, { + "flex flex-col gap-1": isCompact, + }), + heading: cn(sectionClassesProp?.heading, { + hidden: isCompact, + }), + }; + + const itemClasses = { + ...itemClassesProp, + base: cn(itemClassesProp?.base, { + "w-11 h-11 gap-0 p-0": isCompact, + }), + }; + + const renderNestItem = React.useCallback( + (item: SidebarItem) => { + const isNestType = + item.items && + item.items?.length > 0 && + item?.type === SidebarItemType.Nest; + + if (isNestType) { + // Is a nest type item , so we need to remove the href + delete item.href; + } + + return ( + + ) : ( + item.startContent ?? null + ) + } + title={isCompact || isNestType ? null : item.title} + > + {isCompact ? ( + +
+ {item.icon ? ( + + ) : ( + item.startContent ?? null + )} +
+
+ ) : null} + {!isCompact && isNestType ? ( + + + + + {item.title} + +
+ ) : ( + item.startContent ?? null + ) + } + > + {item.items && item.items?.length > 0 ? ( + + {item.items.map(renderItem)} + + ) : ( + renderItem(item) + )} + + + ) : null} + + ); + }, + [isCompact, hideEndContent, iconClassName, items], + ); + + const renderItem = React.useCallback( + (item: SidebarItem) => { + const isNestType = + item.items && + item.items?.length > 0 && + item?.type === SidebarItemType.Nest; + + if (isNestType) { + return renderNestItem(item); + } + + return ( + + ) : ( + item.startContent ?? null + ) + } + textValue={item.title} + title={isCompact ? null : item.title} + > + {isCompact ? ( + +
+ {item.icon ? ( + + ) : ( + item.startContent ?? null + )} +
+
+ ) : null} +
+ ); + }, + [isCompact, hideEndContent, iconClassName, itemClasses?.base], + ); + + return ( + { + const key = Array.from(keys)[0]; + + setSelected(key as React.Key); + onSelect?.(key as string); + }} + {...props} + > + {(item) => { + return item.items && + item.items?.length > 0 && + item?.type === SidebarItemType.Nest ? ( + renderNestItem(item) + ) : item.items && item.items?.length > 0 ? ( + + {item.items.map(renderItem)} + + ) : ( + renderItem(item) + ); + }} + + ); + }, +); + +Sidebar.displayName = "Sidebar"; + +export default Sidebar; diff --git a/components/sidebar/SidebarItems.tsx b/components/sidebar/SidebarItems.tsx new file mode 100644 index 0000000000..53ac6ef72f --- /dev/null +++ b/components/sidebar/SidebarItems.tsx @@ -0,0 +1,503 @@ +import { Icon } from "@iconify/react"; +import { Chip } from "@nextui-org/react"; + +import { SidebarItem, SidebarItemType } from "./Sidebar"; +import TeamAvatar from "./TeamAvatar"; + +/** + * Please check the https://nextui.org/docs/guide/routing to have a seamless router integration + */ + +export const items: SidebarItem[] = [ + { + key: "home", + href: "#", + icon: "solar:home-2-linear", + title: "Home", + }, + { + key: "projects", + href: "#", + icon: "solar:widget-2-outline", + title: "Projects", + endContent: ( + + ), + }, + { + key: "tasks", + href: "#", + icon: "solar:checklist-minimalistic-outline", + title: "Tasks", + endContent: ( + + ), + }, + { + key: "team", + href: "#", + icon: "solar:users-group-two-rounded-outline", + title: "Team", + }, + { + key: "tracker", + href: "#", + icon: "solar:sort-by-time-linear", + title: "Tracker", + endContent: ( + + New + + ), + }, + { + key: "analytics", + href: "#", + icon: "solar:chart-outline", + title: "Analytics", + }, + { + key: "perks", + href: "#", + icon: "solar:gift-linear", + title: "Perks", + endContent: ( + + 3 + + ), + }, + { + key: "expenses", + href: "#", + icon: "solar:bill-list-outline", + title: "Expenses", + }, + { + key: "settings", + href: "#", + icon: "solar:settings-outline", + title: "Settings", + }, +]; + +export const sectionItems: SidebarItem[] = [ + { + key: "dashboards", + title: "Dashboards", + items: [ + { + key: "home", + href: "/", + icon: "solar:home-2-linear", + title: "Overview", + }, + // { + // key: "projects", + // href: "#", + // icon: "solar:widget-2-outline", + // title: "Projects", + // endContent: ( + // + // ), + // }, + // { + // key: "tasks", + // href: "#", + // icon: "solar:checklist-minimalistic-outline", + // title: "Tasks", + // endContent: ( + // + // ), + // }, + { + key: "services", + href: "/services", + icon: "solar:users-group-two-rounded-outline", + title: "Services", + }, + { + key: "compliance", + href: "/compliance", + icon: "solar:sort-by-time-linear", + title: "Compliance", + endContent: ( + + New + + ), + }, + ], + }, + { + key: "scan", + title: "Scan", + items: [ + { + key: "findings", + href: "/findings", + title: "Findings", + icon: "solar:pie-chart-2-outline", + items: [ + { + key: "shareholders", + href: "#", + title: "Shareholders", + }, + { + key: "note_holders", + href: "#", + title: "Note Holders", + }, + { + key: "transactions_log", + href: "#", + title: "Transactions Log", + }, + ], + }, + ], + }, + { + key: "accounts", + title: "Accounts", + items: [ + { + key: "cloud", + href: "/cloud", + icon: "solar:gift-linear", + title: "Cloud", + endContent: ( + + 3 + + ), + }, + { + key: "integration", + href: "/integration", + icon: "solar:bill-list-outline", + title: "Integration", + }, + { + key: "settings", + href: "/settings", + icon: "solar:settings-outline", + title: "Settings", + }, + ], + }, +]; + +export const sectionItemsWithTeams: SidebarItem[] = [ + ...sectionItems, + { + key: "team", + title: "Team", + items: [ + { + key: "users", + href: "#", + title: "Users", + startContent: , + }, + { + key: "roles", + href: "#", + title: "Roles", + startContent: , + }, + ], + }, +]; + +export const brandItems: SidebarItem[] = [ + { + key: "overview", + title: "Overview", + items: [ + { + key: "home", + href: "/", + icon: "solar:home-2-linear", + title: "Home", + }, + { + key: "projects", + href: "#", + icon: "solar:widget-2-outline", + title: "Projects", + endContent: ( + + ), + }, + { + key: "tasks", + href: "#", + icon: "solar:checklist-minimalistic-outline", + title: "Tasks", + endContent: ( + + ), + }, + { + key: "team", + href: "#", + icon: "solar:users-group-two-rounded-outline", + title: "Team", + }, + { + key: "tracker", + href: "#", + icon: "solar:sort-by-time-linear", + title: "Tracker", + endContent: ( + + New + + ), + }, + ], + }, + { + key: "your-teams", + title: "Your Teams", + items: [ + { + key: "nextui", + href: "#", + title: "NextUI", + startContent: ( + + ), + }, + { + key: "tailwind-variants", + href: "#", + title: "Tailwind Variants", + startContent: ( + + ), + }, + { + key: "nextui-pro", + href: "#", + title: "NextUI Pro", + startContent: ( + + ), + }, + ], + }, +]; + +export const sectionLongList: SidebarItem[] = [ + ...sectionItems, + { + key: "payments", + title: "Payments", + items: [ + { + key: "payroll", + href: "#", + title: "Payroll", + icon: "solar:dollar-minimalistic-linear", + }, + { + key: "invoices", + href: "#", + title: "Invoices", + icon: "solar:file-text-linear", + }, + { + key: "billing", + href: "#", + title: "Billing", + icon: "solar:card-outline", + }, + { + key: "payment-methods", + href: "#", + title: "Payment Methods", + icon: "solar:wallet-money-outline", + }, + { + key: "payouts", + href: "#", + title: "Payouts", + icon: "solar:card-transfer-outline", + }, + ], + }, + { + key: "your-teams", + title: "Your Teams", + items: [ + { + key: "nextui", + href: "#", + title: "NextUI", + startContent: , + }, + { + key: "tailwind-variants", + href: "#", + title: "Tailwind Variants", + startContent: , + }, + { + key: "nextui-pro", + href: "#", + title: "NextUI Pro", + startContent: , + }, + ], + }, +]; + +export const sectionNestedItems: SidebarItem[] = [ + { + key: "home", + href: "#", + icon: "solar:home-2-linear", + title: "Home", + }, + { + key: "projects", + href: "#", + icon: "solar:widget-2-outline", + title: "Projects", + endContent: ( + + ), + }, + { + key: "tasks", + href: "#", + icon: "solar:checklist-minimalistic-outline", + title: "Tasks", + endContent: ( + + ), + }, + { + key: "team", + href: "#", + icon: "solar:users-group-two-rounded-outline", + title: "Team", + }, + { + key: "tracker", + href: "#", + icon: "solar:sort-by-time-linear", + title: "Tracker", + endContent: ( + + New + + ), + }, + { + key: "analytics", + href: "#", + icon: "solar:chart-outline", + title: "Analytics", + }, + { + key: "perks", + href: "#", + icon: "solar:gift-linear", + title: "Perks", + endContent: ( + + 3 + + ), + }, + { + key: "cap_table", + title: "Cap Table", + icon: "solar:pie-chart-2-outline", + type: SidebarItemType.Nest, + items: [ + { + key: "shareholders", + icon: "solar:users-group-rounded-linear", + href: "#", + title: "Shareholders", + }, + { + key: "note_holders", + icon: "solar:notes-outline", + href: "#", + title: "Note Holders", + }, + { + key: "transactions_log", + icon: "solar:clipboard-list-linear", + href: "#", + title: "Transactions Log", + }, + ], + }, + { + key: "expenses", + href: "#", + icon: "solar:bill-list-outline", + title: "Expenses", + }, +]; diff --git a/components/sidebar/SidebarWrap.tsx b/components/sidebar/SidebarWrap.tsx new file mode 100644 index 0000000000..533b1819fc --- /dev/null +++ b/components/sidebar/SidebarWrap.tsx @@ -0,0 +1,191 @@ +"use client"; + +import { Icon } from "@iconify/react"; +import { Button, ScrollShadow, Spacer, Tooltip } from "@nextui-org/react"; +import React from "react"; +import { useMediaQuery } from "usehooks-ts"; + +import { cn } from "@/utils/cn"; + +import { AcmeIcon } from "../acme"; +import { ThemeSwitch } from "../ThemeSwitch"; +import Sidebar from "./Sidebar"; +import { sectionItemsWithTeams } from "./SidebarItems"; +import UserAvatar from "./UserAvatar"; + +export const SidebarWrap = () => { + const [isCollapsed, setIsCollapsed] = React.useState(false); + const isMobile = useMediaQuery("(max-width: 768px)"); + + const isCompact = isCollapsed || isMobile; + + const onToggle = React.useCallback(() => { + setIsCollapsed((prev) => !prev); + }, []); + + return ( +
+
+
+ +
+ + Prowler + +
+ + + + + + + + +
+ + + + + + +
+
+ +
+ +
+
+ + + +
+
+ ); +}; diff --git a/components/sidebar/TeamAvatar.tsx b/components/sidebar/TeamAvatar.tsx new file mode 100644 index 0000000000..e3af5cd985 --- /dev/null +++ b/components/sidebar/TeamAvatar.tsx @@ -0,0 +1,38 @@ +"use client"; + +import type { AvatarProps } from "@nextui-org/react"; +import { Avatar } from "@nextui-org/react"; +import React from "react"; + +import { cn } from "@/utils/cn"; + +const TeamAvatar = React.forwardRef( + ({ name, className, classNames = {}, ...props }, ref) => ( + + (name[0] || "") + (name[name.lastIndexOf(" ") + 1] || "").toUpperCase() + } + name={name} + radius="md" + size="sm" + /> + ), +); + +TeamAvatar.displayName = "TeamAvatar"; + +export default TeamAvatar; diff --git a/components/sidebar/UserAvatar.tsx b/components/sidebar/UserAvatar.tsx new file mode 100644 index 0000000000..4f37a3155d --- /dev/null +++ b/components/sidebar/UserAvatar.tsx @@ -0,0 +1,35 @@ +"use client"; + +import { Avatar } from "@nextui-org/react"; +import React from "react"; + +import { cn } from "@/utils/cn"; + +interface UserAvatarProps { + userName: string; + position: string; + isCompact: boolean; +} +const UserAvatar: React.FC = ({ + userName, + position, + isCompact = false, +}) => { + return ( +
+ +
+

+ {userName} +

+

{position}

+
+
+ ); +}; + +export default UserAvatar; diff --git a/package.json b/package.json index c7ba81e435..3756a3d8e8 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "swr": "^2.2.5" }, "devDependencies": { + "@iconify/react": "^5.0.1", "@types/node": "20.5.7", "@types/react": "18.3.3", "@types/react-dom": "18.3.0", @@ -39,7 +40,8 @@ "postcss": "8.4.38", "tailwind-variants": "0.1.20", "tailwindcss": "3.4.3", - "typescript": "5.0.4" + "typescript": "5.0.4", + "usehooks-ts": "^3.1.0" }, "name": "prowler-next-app", "private": true, diff --git a/tailwind.config.js b/tailwind.config.js index 03a4e22e67..5a6f937328 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,11 +1,11 @@ -import {nextui} from '@nextui-org/theme' +import { nextui } from "@nextui-org/theme"; /** @type {import('tailwindcss').Config} */ module.exports = { content: [ - './components/**/*.{js,ts,jsx,tsx,mdx}', - './app/**/*.{js,ts,jsx,tsx,mdx}', - './node_modules/@nextui-org/theme/dist/**/*.{js,ts,jsx,tsx}' + "./components/**/*.{js,ts,jsx,tsx,mdx}", + "./app/**/*.{js,ts,jsx,tsx,mdx}", + "./node_modules/@nextui-org/theme/dist/**/*.{js,ts,jsx,tsx}", ], theme: { extend: { @@ -13,8 +13,11 @@ module.exports = { sans: ["var(--font-sans)"], mono: ["var(--font-geist-mono)"], }, + height: { + "dvh-minus-16": "calc(100dvh - 116px)", + }, }, }, darkMode: "class", plugins: [nextui()], -} +}; diff --git a/utils/cn.ts b/utils/cn.ts new file mode 100644 index 0000000000..9b89275543 --- /dev/null +++ b/utils/cn.ts @@ -0,0 +1,22 @@ +import type { ClassValue } from "clsx"; +import clsx from "clsx"; +import { twMerge } from "tailwind-merge"; + +// Definition of custom classes you want to combine +const customClasses = new Map([ + ["text-small", "text-small"], + ["text-default-500", "text-default-500"], + // Add more custom classes as needed +]); + +export function cn(...inputs: ClassValue[]) { + // Filter and combine custom classes before passing them to twMerge + const filteredInputs = inputs.map((input) => { + if (typeof input === "string") { + return customClasses.get(input) || input; + } + return input; + }); + + return twMerge(clsx(filteredInputs)); +} From 79966db251d30a02807e13b2a933832eab1acb96 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Thu, 11 Jul 2024 15:31:24 +0200 Subject: [PATCH 030/411] feat(sidebar): add state persistence using localStorage --- app/layout.tsx | 8 +++----- components/sidebar/SidebarWrap.tsx | 24 ++++++++++++++++++++---- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/app/layout.tsx b/app/layout.tsx index 074a962755..c414a0bf59 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -43,11 +43,9 @@ export default function RootLayout({ )} > -
-
- -
{children}
-
+
+ +
{children}
diff --git a/components/sidebar/SidebarWrap.tsx b/components/sidebar/SidebarWrap.tsx index 533b1819fc..81f2e195aa 100644 --- a/components/sidebar/SidebarWrap.tsx +++ b/components/sidebar/SidebarWrap.tsx @@ -2,7 +2,7 @@ import { Icon } from "@iconify/react"; import { Button, ScrollShadow, Spacer, Tooltip } from "@nextui-org/react"; -import React from "react"; +import React, { useCallback, useEffect, useState } from "react"; import { useMediaQuery } from "usehooks-ts"; import { cn } from "@/utils/cn"; @@ -14,15 +14,31 @@ import { sectionItemsWithTeams } from "./SidebarItems"; import UserAvatar from "./UserAvatar"; export const SidebarWrap = () => { - const [isCollapsed, setIsCollapsed] = React.useState(false); + const [isCollapsed, setIsCollapsed] = useState(false); + const [isLoaded, setIsLoaded] = useState(false); + const isMobile = useMediaQuery("(max-width: 768px)"); const isCompact = isCollapsed || isMobile; - const onToggle = React.useCallback(() => { - setIsCollapsed((prev) => !prev); + useEffect(() => { + const savedState = localStorage.getItem("isCollapsed"); + if (savedState !== null) { + setIsCollapsed(JSON.parse(savedState)); + } + setIsLoaded(true); }, []); + const onToggle = useCallback(() => { + setIsCollapsed((prev) => { + const newState = !prev; + localStorage.setItem("isCollapsed", JSON.stringify(newState)); + return newState; + }); + }, []); + + if (!isLoaded) return null; + return (
Date: Thu, 11 Jul 2024 15:31:24 +0200 Subject: [PATCH 031/411] feat(sidebar): add state persistence using localStorage --- app/layout.tsx | 8 +++----- components/sidebar/SidebarWrap.tsx | 24 ++++++++++++++++++++---- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/app/layout.tsx b/app/layout.tsx index 074a962755..c414a0bf59 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -43,11 +43,9 @@ export default function RootLayout({ )} > -
-
- -
{children}
-
+
+ +
{children}
diff --git a/components/sidebar/SidebarWrap.tsx b/components/sidebar/SidebarWrap.tsx index 533b1819fc..81f2e195aa 100644 --- a/components/sidebar/SidebarWrap.tsx +++ b/components/sidebar/SidebarWrap.tsx @@ -2,7 +2,7 @@ import { Icon } from "@iconify/react"; import { Button, ScrollShadow, Spacer, Tooltip } from "@nextui-org/react"; -import React from "react"; +import React, { useCallback, useEffect, useState } from "react"; import { useMediaQuery } from "usehooks-ts"; import { cn } from "@/utils/cn"; @@ -14,15 +14,31 @@ import { sectionItemsWithTeams } from "./SidebarItems"; import UserAvatar from "./UserAvatar"; export const SidebarWrap = () => { - const [isCollapsed, setIsCollapsed] = React.useState(false); + const [isCollapsed, setIsCollapsed] = useState(false); + const [isLoaded, setIsLoaded] = useState(false); + const isMobile = useMediaQuery("(max-width: 768px)"); const isCompact = isCollapsed || isMobile; - const onToggle = React.useCallback(() => { - setIsCollapsed((prev) => !prev); + useEffect(() => { + const savedState = localStorage.getItem("isCollapsed"); + if (savedState !== null) { + setIsCollapsed(JSON.parse(savedState)); + } + setIsLoaded(true); }, []); + const onToggle = useCallback(() => { + setIsCollapsed((prev) => { + const newState = !prev; + localStorage.setItem("isCollapsed", JSON.stringify(newState)); + return newState; + }); + }, []); + + if (!isLoaded) return null; + return (
Date: Fri, 12 Jul 2024 09:39:18 +0200 Subject: [PATCH 032/411] chore: remove NextUI boilerplate code --- app/about/layout.tsx | 13 ----- app/about/page.tsx | 10 ---- app/blog/layout.tsx | 13 ----- app/blog/page.tsx | 9 ---- app/clouds/layout.tsx | 11 ----- app/clouds/page.tsx | 108 ----------------------------------------- app/docs/layout.tsx | 13 ----- app/docs/page.tsx | 9 ---- app/login/layout.tsx | 13 ----- app/login/page.tsx | 66 ------------------------- app/pricing/layout.tsx | 13 ----- app/pricing/page.tsx | 9 ---- 12 files changed, 287 deletions(-) delete mode 100644 app/about/layout.tsx delete mode 100644 app/about/page.tsx delete mode 100644 app/blog/layout.tsx delete mode 100644 app/blog/page.tsx delete mode 100644 app/clouds/layout.tsx delete mode 100644 app/clouds/page.tsx delete mode 100644 app/docs/layout.tsx delete mode 100644 app/docs/page.tsx delete mode 100644 app/login/layout.tsx delete mode 100644 app/login/page.tsx delete mode 100644 app/pricing/layout.tsx delete mode 100644 app/pricing/page.tsx diff --git a/app/about/layout.tsx b/app/about/layout.tsx deleted file mode 100644 index 98956a52ad..0000000000 --- a/app/about/layout.tsx +++ /dev/null @@ -1,13 +0,0 @@ -export default function AboutLayout({ - children, -}: { - children: React.ReactNode; -}) { - return ( -
-
- {children} -
-
- ); -} diff --git a/app/about/page.tsx b/app/about/page.tsx deleted file mode 100644 index 5a85989de9..0000000000 --- a/app/about/page.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { title } from "@/components/primitives"; - -export default function AboutPage() { - return ( -
-

About

-

This is a page with no components on it

-
- ); -} diff --git a/app/blog/layout.tsx b/app/blog/layout.tsx deleted file mode 100644 index 911d0bcf41..0000000000 --- a/app/blog/layout.tsx +++ /dev/null @@ -1,13 +0,0 @@ -export default function BlogLayout({ - children, -}: { - children: React.ReactNode; -}) { - return ( -
-
- {children} -
-
- ); -} diff --git a/app/blog/page.tsx b/app/blog/page.tsx deleted file mode 100644 index c6d0c65b43..0000000000 --- a/app/blog/page.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { title } from "@/components/primitives"; - -export default function BlogPage() { - return ( -
-

Blog

-
- ); -} diff --git a/app/clouds/layout.tsx b/app/clouds/layout.tsx deleted file mode 100644 index d9ca1e80e4..0000000000 --- a/app/clouds/layout.tsx +++ /dev/null @@ -1,11 +0,0 @@ -export default function CloudsLayout({ - children, -}: { - children: React.ReactNode; -}) { - return ( -
-
{children}
-
- ); -} diff --git a/app/clouds/page.tsx b/app/clouds/page.tsx deleted file mode 100644 index d546612378..0000000000 --- a/app/clouds/page.tsx +++ /dev/null @@ -1,108 +0,0 @@ -"use client"; - -// import { PencilSquareIcon } from "@heroicons/react/24/solid"; -// import { TrashIcon } from "@heroicons/react/24/solid"; -// import { EyeIcon } from "@heroicons/react/24/solid"; -import { - // Chip, - // getKeyValue, - Table, - TableBody, - TableCell, - TableColumn, - TableHeader, - TableRow, - // Tooltip, - // User, -} from "@nextui-org/react"; -import React from "react"; -import useSWR from "swr"; - -import { title } from "@/components/primitives"; -import { fetcher } from "@/utils/fetcher"; - -export default function CloudsPage() { - const getAccounts = useSWR( - "http://localhost:8080/api/v1/providers/aws/accounts", - fetcher, - ); - - const getAudits = useSWR( - "http://localhost:8080/api/v1/providers/aws/audits", - fetcher, - ); - - // TODO FIX TYPE CHECKING - const getScanDetails = (account_id: number, detail: string) => { - const scan = - getAudits.data && - getAudits.data.find((audit: any) => audit.account_id === account_id); - - if (detail === "status") { - return scan?.audit_complete && "Completed"; - } - - if (detail === "duration") { - return scan?.audit_duration; - } - - if (detail === "added") { - return new Date(scan?.inserted_at).toDateString(); - } - - return; - }; - - // console.log("### getAccounts data", getAccounts.data); - // console.log("### getAudits data", getAudits.data); - - // TODO FIX TYPE CHECKING - const rowItems = getAccounts.data?.map((row: any) => ( - - {row.aws_account_id} - {row.provider_id} - {row.alias} - {row.connected && "Connected"} - {row.groups.map(String).join(", ")} - {getScanDetails(row.account_id, "status")} - {getScanDetails(row.account_id, "duration")} - TBD - {row.resources} - {getScanDetails(row.account_id, "added")} - - )); - - // TODO IMPLEMENT NEXT UI SPECIFIC TABLE COMPONENT WITH VARIED RENDERING - - return ( -
-

Cloud Accounts

-

- {getAccounts.error && ( - Failed to load - )} - {getAccounts.isLoading && ( - Loading - )} -

- {getAccounts.data && ( -
- - ACCOUNT ID - PROVIDER ID - ALIAS - CONNECTED - GROUP(S) - SCAN STATUS - LAST SCAN - NEXT SCAN - RESOURCES - ADDED - - {rowItems} -
- )} -

This is a page with "use client", useSWR

-
- ); -} diff --git a/app/docs/layout.tsx b/app/docs/layout.tsx deleted file mode 100644 index eaf63f3b99..0000000000 --- a/app/docs/layout.tsx +++ /dev/null @@ -1,13 +0,0 @@ -export default function DocsLayout({ - children, -}: { - children: React.ReactNode; -}) { - return ( -
-
- {children} -
-
- ); -} diff --git a/app/docs/page.tsx b/app/docs/page.tsx deleted file mode 100644 index 4a2f19b357..0000000000 --- a/app/docs/page.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { title } from "@/components/primitives"; - -export default function DocsPage() { - return ( -
-

Docs

-
- ); -} diff --git a/app/login/layout.tsx b/app/login/layout.tsx deleted file mode 100644 index d3a4a58ea4..0000000000 --- a/app/login/layout.tsx +++ /dev/null @@ -1,13 +0,0 @@ -export default function LoginLayout({ - children, -}: { - children: React.ReactNode; -}) { - return ( -
-
- {children} -
-
- ); -} diff --git a/app/login/page.tsx b/app/login/page.tsx deleted file mode 100644 index f07535d803..0000000000 --- a/app/login/page.tsx +++ /dev/null @@ -1,66 +0,0 @@ -"use client"; - -import { EyeIcon } from "@heroicons/react/24/solid"; -import { EyeSlashIcon } from "@heroicons/react/24/solid"; -import { - Button, - Card, - CardBody, - CardFooter, - CardHeader, - Input, - Link, -} from "@nextui-org/react"; -import React from "react"; - -export default function LoginPage() { - const [isVisible, setIsVisible] = React.useState(false); - - const toggleVisibility = () => setIsVisible(!isVisible); - - return ( -
-
- - -

Login

-
- - - - {isVisible ? ( - - ) : ( - - )} - - } - type={isVisible ? "text" : "password"} - className="max-w-xs" - /> - - - - -
-

This is a page with "use client", useState

-
-
- ); -} diff --git a/app/pricing/layout.tsx b/app/pricing/layout.tsx deleted file mode 100644 index dc3db6af64..0000000000 --- a/app/pricing/layout.tsx +++ /dev/null @@ -1,13 +0,0 @@ -export default function PricingLayout({ - children, -}: { - children: React.ReactNode; -}) { - return ( -
-
- {children} -
-
- ); -} diff --git a/app/pricing/page.tsx b/app/pricing/page.tsx deleted file mode 100644 index 42e233392d..0000000000 --- a/app/pricing/page.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { title } from "@/components/primitives"; - -export default function PricingPage() { - return ( -
-

Pricing

-
- ); -} From c427878820ee933a5bd46d374901e02e5a09505d Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Fri, 12 Jul 2024 09:39:36 +0200 Subject: [PATCH 033/411] chore: remove NextUI boilerplate code --- components/acme.tsx | 25 ------------------------- 1 file changed, 25 deletions(-) delete mode 100644 components/acme.tsx diff --git a/components/acme.tsx b/components/acme.tsx deleted file mode 100644 index 9478723fa5..0000000000 --- a/components/acme.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import React from "react"; - -import { IconSvgProps } from "../types/index"; - -export const AcmeIcon: React.FC = ({ - size = 32, - width, - height, - ...props -}) => ( - - - -); From 0bef1a157b2f32f831363b6f31de6f6e9e7d0248 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Fri, 12 Jul 2024 09:42:37 +0200 Subject: [PATCH 034/411] feat: centralize exports with index.ts in all directories --- app/layout.tsx | 4 +- components/ThemeSwitch.tsx | 2 +- components/{ => icons}/Icons.tsx | 0 components/icons/index.ts | 2 + components/icons/prowler/ProwlerIcons.tsx | 56 ++++++++++++++++++++ components/index.ts | 1 + components/{ => ui}/sidebar/Sidebar.tsx | 0 components/{ => ui}/sidebar/SidebarItems.tsx | 0 components/{ => ui}/sidebar/SidebarWrap.tsx | 43 ++++++++------- components/{ => ui}/sidebar/TeamAvatar.tsx | 0 components/{ => ui}/sidebar/UserAvatar.tsx | 0 components/ui/sidebar/index.ts | 5 ++ 12 files changed, 91 insertions(+), 22 deletions(-) rename components/{ => icons}/Icons.tsx (100%) create mode 100644 components/icons/index.ts create mode 100644 components/icons/prowler/ProwlerIcons.tsx create mode 100644 components/index.ts rename components/{ => ui}/sidebar/Sidebar.tsx (100%) rename components/{ => ui}/sidebar/SidebarItems.tsx (100%) rename components/{ => ui}/sidebar/SidebarWrap.tsx (88%) rename components/{ => ui}/sidebar/TeamAvatar.tsx (100%) rename components/{ => ui}/sidebar/UserAvatar.tsx (100%) create mode 100644 components/ui/sidebar/index.ts diff --git a/app/layout.tsx b/app/layout.tsx index c414a0bf59..ebc4cb3fad 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -4,10 +4,10 @@ import clsx from "clsx"; import { Metadata, Viewport } from "next"; import React from "react"; +import { SidebarWrap } from "@/components"; import { fontSans } from "@/config/fonts"; import { siteConfig } from "@/config/site"; -import { SidebarWrap } from "../components/sidebar/SidebarWrap"; import { Providers } from "./providers"; export const metadata: Metadata = { @@ -42,7 +42,7 @@ export default function RootLayout({ fontSans.variable, )} > - +
{children}
diff --git a/components/ThemeSwitch.tsx b/components/ThemeSwitch.tsx index 9c58062ce2..1e581560ae 100644 --- a/components/ThemeSwitch.tsx +++ b/components/ThemeSwitch.tsx @@ -8,7 +8,7 @@ import { useTheme } from "next-themes"; import { FC } from "react"; import React from "react"; -import { MoonFilledIcon, SunFilledIcon } from "./Icons"; +import { MoonFilledIcon, SunFilledIcon } from "./icons"; export interface ThemeSwitchProps { className?: string; diff --git a/components/Icons.tsx b/components/icons/Icons.tsx similarity index 100% rename from components/Icons.tsx rename to components/icons/Icons.tsx diff --git a/components/icons/index.ts b/components/icons/index.ts new file mode 100644 index 0000000000..e733eda3ef --- /dev/null +++ b/components/icons/index.ts @@ -0,0 +1,2 @@ +export * from "./Icons"; +export * from "./prowler/ProwlerIcons"; diff --git a/components/icons/prowler/ProwlerIcons.tsx b/components/icons/prowler/ProwlerIcons.tsx new file mode 100644 index 0000000000..868cfaad48 --- /dev/null +++ b/components/icons/prowler/ProwlerIcons.tsx @@ -0,0 +1,56 @@ +import React from "react"; + +import { IconSvgProps } from "../../../types/index"; + +export const ProwlerExtended: React.FC = ({ + size, + width = 200, + height, + ...props +}) => ( + + + +); + +export const ProwlerShort: React.FC = ({ + size, + width = 30, + height, + ...props +}) => ( + + + +); diff --git a/components/index.ts b/components/index.ts new file mode 100644 index 0000000000..d263137d94 --- /dev/null +++ b/components/index.ts @@ -0,0 +1 @@ +export * from "./ui/sidebar"; diff --git a/components/sidebar/Sidebar.tsx b/components/ui/sidebar/Sidebar.tsx similarity index 100% rename from components/sidebar/Sidebar.tsx rename to components/ui/sidebar/Sidebar.tsx diff --git a/components/sidebar/SidebarItems.tsx b/components/ui/sidebar/SidebarItems.tsx similarity index 100% rename from components/sidebar/SidebarItems.tsx rename to components/ui/sidebar/SidebarItems.tsx diff --git a/components/sidebar/SidebarWrap.tsx b/components/ui/sidebar/SidebarWrap.tsx similarity index 88% rename from components/sidebar/SidebarWrap.tsx rename to components/ui/sidebar/SidebarWrap.tsx index 81f2e195aa..162e389479 100644 --- a/components/sidebar/SidebarWrap.tsx +++ b/components/ui/sidebar/SidebarWrap.tsx @@ -2,13 +2,17 @@ import { Icon } from "@iconify/react"; import { Button, ScrollShadow, Spacer, Tooltip } from "@nextui-org/react"; +import clsx from "clsx"; import React, { useCallback, useEffect, useState } from "react"; import { useMediaQuery } from "usehooks-ts"; import { cn } from "@/utils/cn"; -import { AcmeIcon } from "../acme"; -import { ThemeSwitch } from "../ThemeSwitch"; +import { + ProwlerExtended, + ProwlerShort, +} from "../../icons/prowler/ProwlerIcons"; +import { ThemeSwitch } from "../../ThemeSwitch"; import Sidebar from "./Sidebar"; import { sectionItemsWithTeams } from "./SidebarItems"; import UserAvatar from "./UserAvatar"; @@ -41,32 +45,33 @@ export const SidebarWrap = () => { return (
-
- -
- - Prowler - + +
+
+ +
diff --git a/components/sidebar/TeamAvatar.tsx b/components/ui/sidebar/TeamAvatar.tsx similarity index 100% rename from components/sidebar/TeamAvatar.tsx rename to components/ui/sidebar/TeamAvatar.tsx diff --git a/components/sidebar/UserAvatar.tsx b/components/ui/sidebar/UserAvatar.tsx similarity index 100% rename from components/sidebar/UserAvatar.tsx rename to components/ui/sidebar/UserAvatar.tsx diff --git a/components/ui/sidebar/index.ts b/components/ui/sidebar/index.ts new file mode 100644 index 0000000000..ec61cb2f3b --- /dev/null +++ b/components/ui/sidebar/index.ts @@ -0,0 +1,5 @@ +export * from "./Sidebar"; +export * from "./SidebarItems"; +export * from "./SidebarWrap"; +export * from "./TeamAvatar"; +export * from "./UserAvatar"; From 08059e3a32503275c5d459da7b5265339def2b27 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Fri, 12 Jul 2024 09:58:09 +0200 Subject: [PATCH 035/411] refactor: clean up and organize exports in sidebar components --- components/ui/sidebar/SidebarItems.tsx | 2 +- components/ui/sidebar/SidebarWrap.tsx | 2 +- components/ui/sidebar/TeamAvatar.tsx | 4 +--- components/ui/sidebar/UserAvatar.tsx | 4 +--- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/components/ui/sidebar/SidebarItems.tsx b/components/ui/sidebar/SidebarItems.tsx index 53ac6ef72f..88e36378a6 100644 --- a/components/ui/sidebar/SidebarItems.tsx +++ b/components/ui/sidebar/SidebarItems.tsx @@ -2,7 +2,7 @@ import { Icon } from "@iconify/react"; import { Chip } from "@nextui-org/react"; import { SidebarItem, SidebarItemType } from "./Sidebar"; -import TeamAvatar from "./TeamAvatar"; +import { TeamAvatar } from "./TeamAvatar"; /** * Please check the https://nextui.org/docs/guide/routing to have a seamless router integration diff --git a/components/ui/sidebar/SidebarWrap.tsx b/components/ui/sidebar/SidebarWrap.tsx index 162e389479..86f23ce076 100644 --- a/components/ui/sidebar/SidebarWrap.tsx +++ b/components/ui/sidebar/SidebarWrap.tsx @@ -15,7 +15,7 @@ import { import { ThemeSwitch } from "../../ThemeSwitch"; import Sidebar from "./Sidebar"; import { sectionItemsWithTeams } from "./SidebarItems"; -import UserAvatar from "./UserAvatar"; +import { UserAvatar } from "./UserAvatar"; export const SidebarWrap = () => { const [isCollapsed, setIsCollapsed] = useState(false); diff --git a/components/ui/sidebar/TeamAvatar.tsx b/components/ui/sidebar/TeamAvatar.tsx index e3af5cd985..246704ebff 100644 --- a/components/ui/sidebar/TeamAvatar.tsx +++ b/components/ui/sidebar/TeamAvatar.tsx @@ -6,7 +6,7 @@ import React from "react"; import { cn } from "@/utils/cn"; -const TeamAvatar = React.forwardRef( +export const TeamAvatar = React.forwardRef( ({ name, className, classNames = {}, ...props }, ref) => ( ( ); TeamAvatar.displayName = "TeamAvatar"; - -export default TeamAvatar; diff --git a/components/ui/sidebar/UserAvatar.tsx b/components/ui/sidebar/UserAvatar.tsx index 4f37a3155d..9c7586f550 100644 --- a/components/ui/sidebar/UserAvatar.tsx +++ b/components/ui/sidebar/UserAvatar.tsx @@ -10,7 +10,7 @@ interface UserAvatarProps { position: string; isCompact: boolean; } -const UserAvatar: React.FC = ({ +export const UserAvatar: React.FC = ({ userName, position, isCompact = false, @@ -31,5 +31,3 @@ const UserAvatar: React.FC = ({
); }; - -export default UserAvatar; From e7d4143f4714c31925b6bb31a5cf32f243d474ba Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Fri, 12 Jul 2024 10:05:42 +0200 Subject: [PATCH 036/411] chore: use clsx library instead of custom cn utility for managing class names --- components/ui/sidebar/Sidebar.tsx | 33 +++++++++++++-------------- components/ui/sidebar/SidebarWrap.tsx | 14 +++++------- components/ui/sidebar/TeamAvatar.tsx | 7 +++--- components/ui/sidebar/UserAvatar.tsx | 5 ++-- utils/cn.ts | 22 ------------------ 5 files changed, 27 insertions(+), 54 deletions(-) delete mode 100644 utils/cn.ts diff --git a/components/ui/sidebar/Sidebar.tsx b/components/ui/sidebar/Sidebar.tsx index 49df2deac7..3a677ea20e 100644 --- a/components/ui/sidebar/Sidebar.tsx +++ b/components/ui/sidebar/Sidebar.tsx @@ -14,10 +14,9 @@ import { ListboxSection, Tooltip, } from "@nextui-org/react"; +import clsx from "clsx"; import React from "react"; -import { cn } from "@/utils/cn"; - export enum SidebarItemType { Nest = "nest", } @@ -67,20 +66,20 @@ const Sidebar = React.forwardRef( const sectionClasses = { ...sectionClassesProp, - base: cn(sectionClassesProp?.base, "w-full", { + base: clsx(sectionClassesProp?.base, "w-full", { "p-0 max-w-[44px]": isCompact, }), - group: cn(sectionClassesProp?.group, { + group: clsx(sectionClassesProp?.group, { "flex flex-col gap-1": isCompact, }), - heading: cn(sectionClassesProp?.heading, { + heading: clsx(sectionClassesProp?.heading, { hidden: isCompact, }), }; const itemClasses = { ...itemClassesProp, - base: cn(itemClassesProp?.base, { + base: clsx(itemClassesProp?.base, { "w-11 h-11 gap-0 p-0": isCompact, }), }; @@ -102,7 +101,7 @@ const Sidebar = React.forwardRef( {...item} key={item.key} classNames={{ - base: cn( + base: clsx( { "h-auto p-0": !isCompact && isNestType, }, @@ -119,7 +118,7 @@ const Sidebar = React.forwardRef( startContent={ isCompact || isNestType ? null : item.icon ? ( (
{item.icon ? ( ( className={"flex h-11 items-center gap-2 px-2 py-1.5"} > ( ( startContent={ isCompact ? null : item.icon ? ( (
{item.icon ? ( ( ref={ref} hideSelectedIcon as="nav" - className={cn("list-none", className)} + className={clsx("list-none", className)} classNames={{ ...classNames, - list: cn("items-center", classNames?.list), + list: clsx("items-center", classNames?.list), }} color="default" itemClasses={{ ...itemClasses, - base: cn( + base: clsx( "px-3 min-h-11 rounded-large h-[44px] data-[selected=true]:bg-default-100", itemClasses?.base, ), - title: cn( + title: clsx( "text-small font-medium text-default-500 group-data-[selected=true]:text-foreground", itemClasses?.title, ), diff --git a/components/ui/sidebar/SidebarWrap.tsx b/components/ui/sidebar/SidebarWrap.tsx index 86f23ce076..75c43aa853 100644 --- a/components/ui/sidebar/SidebarWrap.tsx +++ b/components/ui/sidebar/SidebarWrap.tsx @@ -6,8 +6,6 @@ import clsx from "clsx"; import React, { useCallback, useEffect, useState } from "react"; import { useMediaQuery } from "usehooks-ts"; -import { cn } from "@/utils/cn"; - import { ProwlerExtended, ProwlerShort, @@ -90,7 +88,7 @@ export const SidebarWrap = () => {
@@ -101,7 +99,7 @@ export const SidebarWrap = () => { >
@@ -172,7 +170,7 @@ export const SidebarWrap = () => { placement={isCompact ? "right" : "top"} >
{ placement={isCompact ? "right" : "top"} > -

Overview

- -
-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-

hi hi from the overview page

-
- - - ); -} diff --git a/components/index.ts b/components/index.ts index d263137d94..97ad46bc7f 100644 --- a/components/index.ts +++ b/components/index.ts @@ -1 +1,2 @@ +export * from "./ui/header/Header"; export * from "./ui/sidebar"; diff --git a/components/ui/header/Header.tsx b/components/ui/header/Header.tsx new file mode 100644 index 0000000000..bc1dc817fa --- /dev/null +++ b/components/ui/header/Header.tsx @@ -0,0 +1,22 @@ +import { Icon } from "@iconify/react"; +import { Divider } from "@nextui-org/react"; +import React from "react"; + +interface HeaderProps { + title: string; + icon: string; +} + +const Header: React.FC = ({ title, icon }) => { + return ( + <> +
+ +

{title}

+
+ + + ); +}; + +export default Header; diff --git a/components/ui/index.ts b/components/ui/index.ts new file mode 100644 index 0000000000..9efbf2a414 --- /dev/null +++ b/components/ui/index.ts @@ -0,0 +1,2 @@ +export * from "./header/Header"; +export * from "./sidebar"; diff --git a/components/ui/sidebar/SidebarWrap.tsx b/components/ui/sidebar/SidebarWrap.tsx index 75c43aa853..6d61e2b726 100644 --- a/components/ui/sidebar/SidebarWrap.tsx +++ b/components/ui/sidebar/SidebarWrap.tsx @@ -161,8 +161,9 @@ export const SidebarWrap = () => {
Date: Fri, 12 Jul 2024 12:29:26 +0200 Subject: [PATCH 038/411] chore: remove unused icons --- app/(prowler)/layout.tsx | 4 +- components/icons/Icons.tsx | 96 -------------------------------------- 2 files changed, 3 insertions(+), 97 deletions(-) diff --git a/app/(prowler)/layout.tsx b/app/(prowler)/layout.tsx index 4988405236..c4d78f6405 100644 --- a/app/(prowler)/layout.tsx +++ b/app/(prowler)/layout.tsx @@ -45,7 +45,9 @@ export default function RootLayout({
-
{children}
+
+ {children} +
diff --git a/components/icons/Icons.tsx b/components/icons/Icons.tsx index a3d73323b4..23ccf84691 100644 --- a/components/icons/Icons.tsx +++ b/components/icons/Icons.tsx @@ -2,49 +2,6 @@ import * as React from "react"; import { IconSvgProps } from "@/types"; -export const Logo: React.FC = ({ - size = 36, - width, - height, - ...props -}) => ( - - - -); - -export const DiscordIcon: React.FC = ({ - size = 24, - width, - height, - ...props -}) => { - return ( - - - - ); -}; - export const TwitterIcon: React.FC = ({ size = 24, width, @@ -133,31 +90,6 @@ export const SunFilledIcon = ({ ); -export const HeartFilledIcon = ({ - size = 24, - width, - height, - ...props -}: IconSvgProps) => ( - -); - export const SearchIcon = (props: IconSvgProps) => ( ); - -export const NextUILogo: React.FC = (props) => { - const { width, height = 40 } = props; - - return ( - - - - - - ); -}; From 3edb2ea9f200e6f29cb3aac626f48b61307d018d Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Mon, 15 Jul 2024 10:51:23 +0200 Subject: [PATCH 039/411] refactor: rename cloud page to providers page --- app/(prowler)/cloud/page.tsx | 13 ------------- app/(prowler)/providers/page.tsx | 13 +++++++++++++ components/ui/sidebar/SidebarItems.tsx | 6 +++--- 3 files changed, 16 insertions(+), 16 deletions(-) delete mode 100644 app/(prowler)/cloud/page.tsx create mode 100644 app/(prowler)/providers/page.tsx diff --git a/app/(prowler)/cloud/page.tsx b/app/(prowler)/cloud/page.tsx deleted file mode 100644 index 0213012c6c..0000000000 --- a/app/(prowler)/cloud/page.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import React from "react"; - -import Header from "@/components/ui/header/Header"; - -export default function Cloud() { - return ( - <> -
- -

Hi hi from Cloud page

- - ); -} diff --git a/app/(prowler)/providers/page.tsx b/app/(prowler)/providers/page.tsx new file mode 100644 index 0000000000..3c82c701b4 --- /dev/null +++ b/app/(prowler)/providers/page.tsx @@ -0,0 +1,13 @@ +import React from "react"; + +import Header from "@/components/ui/header/Header"; + +export default function Providers() { + return ( + <> +
+ +

Hi hi from Providers page

+ + ); +} diff --git a/components/ui/sidebar/SidebarItems.tsx b/components/ui/sidebar/SidebarItems.tsx index 88e36378a6..9d81de6740 100644 --- a/components/ui/sidebar/SidebarItems.tsx +++ b/components/ui/sidebar/SidebarItems.tsx @@ -179,10 +179,10 @@ export const sectionItems: SidebarItem[] = [ title: "Accounts", items: [ { - key: "cloud", - href: "/cloud", + key: "providers", + href: "/providers", icon: "solar:gift-linear", - title: "Cloud", + title: "Providers", endContent: ( 3 From 93e44a601918ffb8e175e18cd35dcba351d9b2ec Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Mon, 15 Jul 2024 11:07:06 +0200 Subject: [PATCH 040/411] fix: fix width for ProwlerExtended logo --- components/icons/prowler/ProwlerIcons.tsx | 2 +- components/ui/sidebar/SidebarWrap.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/components/icons/prowler/ProwlerIcons.tsx b/components/icons/prowler/ProwlerIcons.tsx index 868cfaad48..17a1c8dd72 100644 --- a/components/icons/prowler/ProwlerIcons.tsx +++ b/components/icons/prowler/ProwlerIcons.tsx @@ -4,7 +4,7 @@ import { IconSvgProps } from "../../../types/index"; export const ProwlerExtended: React.FC = ({ size, - width = 200, + width = 216, height, ...props }) => ( diff --git a/components/ui/sidebar/SidebarWrap.tsx b/components/ui/sidebar/SidebarWrap.tsx index 6d61e2b726..7f9b0e62e2 100644 --- a/components/ui/sidebar/SidebarWrap.tsx +++ b/components/ui/sidebar/SidebarWrap.tsx @@ -52,8 +52,8 @@ export const SidebarWrap = () => { )} >
Date: Mon, 15 Jul 2024 20:42:54 +0200 Subject: [PATCH 041/411] Make the link active when visiting the page (#9) * chore: set overview as a default selected key in the sidebar * feat: use the usePathname hook from Next.js App Router to get the current pathname and use it as the active key for the Sidebar component. * feat: make it works also for / the overview page --- components/ui/sidebar/SidebarItems.tsx | 2 +- components/ui/sidebar/SidebarWrap.tsx | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/components/ui/sidebar/SidebarItems.tsx b/components/ui/sidebar/SidebarItems.tsx index 9d81de6740..e6423fff62 100644 --- a/components/ui/sidebar/SidebarItems.tsx +++ b/components/ui/sidebar/SidebarItems.tsx @@ -95,7 +95,7 @@ export const sectionItems: SidebarItem[] = [ title: "Dashboards", items: [ { - key: "home", + key: "overview", href: "/", icon: "solar:home-2-linear", title: "Overview", diff --git a/components/ui/sidebar/SidebarWrap.tsx b/components/ui/sidebar/SidebarWrap.tsx index 7f9b0e62e2..f2212850bb 100644 --- a/components/ui/sidebar/SidebarWrap.tsx +++ b/components/ui/sidebar/SidebarWrap.tsx @@ -3,6 +3,7 @@ import { Icon } from "@iconify/react"; import { Button, ScrollShadow, Spacer, Tooltip } from "@nextui-org/react"; import clsx from "clsx"; +import { usePathname } from "next/navigation"; import React, { useCallback, useEffect, useState } from "react"; import { useMediaQuery } from "usehooks-ts"; @@ -19,6 +20,9 @@ export const SidebarWrap = () => { const [isCollapsed, setIsCollapsed] = useState(false); const [isLoaded, setIsLoaded] = useState(false); + const pathname = usePathname(); + const currentPath = pathname === "/" ? "overview" : pathname.split("/")?.[1]; + const isMobile = useMediaQuery("(max-width: 768px)"); const isCompact = isCollapsed || isMobile; @@ -74,16 +78,17 @@ export const SidebarWrap = () => { From 0bdfa1a3b9d4ac0d3719ad1be7e0dc6d0930774c Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Tue, 16 Jul 2024 15:06:47 +0200 Subject: [PATCH 042/411] Chore: Update import paths for consistency (#10) --- app/(prowler)/compliance/page.tsx | 2 +- app/(prowler)/findings/page.tsx | 2 +- app/(prowler)/integration/page.tsx | 2 +- app/(prowler)/page.tsx | 2 +- app/(prowler)/providers/page.tsx | 2 +- app/(prowler)/services/page.tsx | 2 +- components/icons/Icons.tsx | 98 +++++++++++++++++++++++++++--- components/ui/header/Header.tsx | 4 +- components/ui/index.ts | 2 - next.config.js | 5 +- tsconfig.json | 17 +----- 11 files changed, 102 insertions(+), 36 deletions(-) delete mode 100644 components/ui/index.ts diff --git a/app/(prowler)/compliance/page.tsx b/app/(prowler)/compliance/page.tsx index 0b23923ac7..c36b794648 100644 --- a/app/(prowler)/compliance/page.tsx +++ b/app/(prowler)/compliance/page.tsx @@ -1,6 +1,6 @@ import React from "react"; -import Header from "@/components/ui/header/Header"; +import { Header } from "@/components"; export default function Compliance() { return ( diff --git a/app/(prowler)/findings/page.tsx b/app/(prowler)/findings/page.tsx index 278ee957bc..42395336c7 100644 --- a/app/(prowler)/findings/page.tsx +++ b/app/(prowler)/findings/page.tsx @@ -1,6 +1,6 @@ import React from "react"; -import Header from "@/components/ui/header/Header"; +import { Header } from "@/components"; export default function Findings() { return ( diff --git a/app/(prowler)/integration/page.tsx b/app/(prowler)/integration/page.tsx index 189c48b5b8..81596cc67e 100644 --- a/app/(prowler)/integration/page.tsx +++ b/app/(prowler)/integration/page.tsx @@ -1,6 +1,6 @@ import React from "react"; -import Header from "@/components/ui/header/Header"; +import { Header } from "@/components"; export default function Integration() { return ( diff --git a/app/(prowler)/page.tsx b/app/(prowler)/page.tsx index 96986bab92..d33fc3e26e 100644 --- a/app/(prowler)/page.tsx +++ b/app/(prowler)/page.tsx @@ -1,6 +1,6 @@ import React from "react"; -import Header from "@/components/ui/header/Header"; +import { Header } from "@/components"; export default function Home() { return ( diff --git a/app/(prowler)/providers/page.tsx b/app/(prowler)/providers/page.tsx index 3c82c701b4..d2e924c571 100644 --- a/app/(prowler)/providers/page.tsx +++ b/app/(prowler)/providers/page.tsx @@ -1,6 +1,6 @@ import React from "react"; -import Header from "@/components/ui/header/Header"; +import { Header } from "@/components"; export default function Providers() { return ( diff --git a/app/(prowler)/services/page.tsx b/app/(prowler)/services/page.tsx index 436f2fac93..26692e848d 100644 --- a/app/(prowler)/services/page.tsx +++ b/app/(prowler)/services/page.tsx @@ -1,6 +1,6 @@ import React from "react"; -import Header from "@/components/ui/header/Header"; +import { Header } from "@/components"; export default function Services() { return ( diff --git a/components/icons/Icons.tsx b/components/icons/Icons.tsx index 23ccf84691..0f0cd7c60d 100644 --- a/components/icons/Icons.tsx +++ b/components/icons/Icons.tsx @@ -46,12 +46,12 @@ export const GithubIcon: React.FC = ({ ); }; -export const MoonFilledIcon = ({ +export const MoonFilledIcon: React.FC = ({ size = 24, width, height, ...props -}: IconSvgProps) => ( +}) => (

Hi hi from Providers page

+ + +

Modal body content

+ + } + actionText="Save" + onAction={onAction} + /> ); } diff --git a/components/Navbar.tsx b/components/Navbar.tsx deleted file mode 100644 index 2471830968..0000000000 --- a/components/Navbar.tsx +++ /dev/null @@ -1,60 +0,0 @@ -"use client"; - -import { - Link, - Navbar as NextUINavbar, - NavbarBrand, - NavbarContent, - NavbarItem, - NavbarMenu, - NavbarMenuItem, - NavbarMenuToggle, -} from "@nextui-org/react"; -import React from "react"; - -export const Navbar = () => { - const [isMenuOpen, setIsMenuOpen] = React.useState(false); - - const menuItems = ["Test Link", "Log Out"]; - - return ( - - - - -

PROWLER

-
-
- - - - - Login - - - - - Cloud Accounts - - - - - About - - - - - - {menuItems.map((item, index) => ( - - - {item} - - - ))} - -
- ); -}; diff --git a/components/index.ts b/components/index.ts index 97ad46bc7f..3b12cae205 100644 --- a/components/index.ts +++ b/components/index.ts @@ -1,2 +1,3 @@ export * from "./ui/header/Header"; +export * from "./ui/modal/Modal"; export * from "./ui/sidebar"; diff --git a/components/ui/modal/Modal.tsx b/components/ui/modal/Modal.tsx new file mode 100644 index 0000000000..d450c6e858 --- /dev/null +++ b/components/ui/modal/Modal.tsx @@ -0,0 +1,54 @@ +import { + Button, + Modal as ModalContainer, + ModalBody, + ModalContent, + ModalFooter, + ModalHeader, +} from "@nextui-org/react"; +import React from "react"; + +interface ModalProps { + title: string; + isOpen: boolean; + onOpenChange: (isOpen: boolean) => void; + body: React.ReactNode; + actionText?: string; + onAction?: () => void; +} + +export const Modal: React.FC = ({ + title, + isOpen, + onOpenChange, + body, + actionText, + onAction, +}) => { + const hasActionButton = actionText && onAction; + + return ( + <> + + + {(onClose) => ( + <> + {title} + {body} + + + {hasActionButton && ( + + )} + + + )} + + + + ); +}; diff --git a/package.json b/package.json index 3756a3d8e8..7fabe0be89 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,5 @@ { "dependencies": { - "@heroicons/react": "^2.1.4", "@nextui-org/react": "^2.4.2", "@nextui-org/system": "2.2.1", "@nextui-org/theme": "2.2.5", From bfa98646c1df8fb2f72f8f3c87ad0931c50ef500 Mon Sep 17 00:00:00 2001 From: Sophia Dao Date: Tue, 16 Jul 2024 15:51:58 -0500 Subject: [PATCH 044/411] feat(modal): Change name of modal --- app/(prowler)/providers/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/(prowler)/providers/page.tsx b/app/(prowler)/providers/page.tsx index 6a3f11163b..60af8b9e49 100644 --- a/app/(prowler)/providers/page.tsx +++ b/app/(prowler)/providers/page.tsx @@ -21,7 +21,7 @@ export default function Providers() {

Modal body content

From ec7df134b48e08ea8930498b1acb9e8a97fa7556 Mon Sep 17 00:00:00 2001 From: Sophia Dao Date: Wed, 17 Jul 2024 12:43:33 -0500 Subject: [PATCH 045/411] feat(modal): make code review changes --- components/ui/modal/Modal.tsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/components/ui/modal/Modal.tsx b/components/ui/modal/Modal.tsx index d450c6e858..7c18d750a6 100644 --- a/components/ui/modal/Modal.tsx +++ b/components/ui/modal/Modal.tsx @@ -15,6 +15,7 @@ interface ModalProps { body: React.ReactNode; actionText?: string; onAction?: () => void; + isDismissable?: boolean; } export const Modal: React.FC = ({ @@ -24,12 +25,20 @@ export const Modal: React.FC = ({ body, actionText, onAction, + isDismissable = true, }) => { const hasActionButton = actionText && onAction; return ( <> - + {(onClose) => ( <> From 7093261f8409cc17e52ddd341254c1f5dafab97c Mon Sep 17 00:00:00 2001 From: Sophia Dao Date: Wed, 17 Jul 2024 12:47:25 -0500 Subject: [PATCH 046/411] feat(modal): Add in prop for Close button text --- app/(prowler)/providers/page.tsx | 1 - components/ui/modal/Modal.tsx | 6 ++++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/(prowler)/providers/page.tsx b/app/(prowler)/providers/page.tsx index 60af8b9e49..edb487b987 100644 --- a/app/(prowler)/providers/page.tsx +++ b/app/(prowler)/providers/page.tsx @@ -27,7 +27,6 @@ export default function Providers() {

Modal body content

} - actionText="Save" onAction={onAction} /> diff --git a/components/ui/modal/Modal.tsx b/components/ui/modal/Modal.tsx index 7c18d750a6..1344ec9ecf 100644 --- a/components/ui/modal/Modal.tsx +++ b/components/ui/modal/Modal.tsx @@ -13,6 +13,7 @@ interface ModalProps { isOpen: boolean; onOpenChange: (isOpen: boolean) => void; body: React.ReactNode; + onCloseText?: string; actionText?: string; onAction?: () => void; isDismissable?: boolean; @@ -23,7 +24,8 @@ export const Modal: React.FC = ({ isOpen, onOpenChange, body, - actionText, + onCloseText = "Close", + actionText = "Save", onAction, isDismissable = true, }) => { @@ -46,7 +48,7 @@ export const Modal: React.FC = ({ {body} {hasActionButton && ( + + + Manage + Delete + + +
+ ); + default: + return cellValue; + } + }, + [], + ); + + const onRowsPerPageChange = React.useCallback( + (e: React.ChangeEvent) => { + setRowsPerPage(Number(e.target.value)); + setPage(1); + }, + [], + ); + + const onSearchChange = React.useCallback((value: string) => { + if (value) { + setFilterValue(value); + setPage(1); + } else { + setFilterValue(""); + } + }, []); + + const topContent = React.useMemo(() => { + return ( +
+
+ {/* } + value={filterValue} + variant="bordered" + onClear={() => setFilterValue("")} + onValueChange={onSearchChange} + /> */} +
+ {/* + + + + + {statusOptions.map((status) => ( + + {capitalize(status.name)} + + ))} + + + + + + + + {columns.map((column) => ( + + {capitalize(column.name)} + + ))} + + */} + +
+
+
+ + Total {providers.length} entries + + {items.length < initialRowsPerPage &&
hi hi que pasa
} + +
+
+ ); + }, [ + filterValue, + statusFilter, + visibleColumns, + onSearchChange, + onRowsPerPageChange, + providers.length, + hasSearchFilter, + ]); + + const bottomContent = React.useMemo(() => { + return ( +
+ + {/* {selectionMode !== "none" && ( + + {selectedKeys === "all" + ? "All items selected" + : `${selectedKeys.size} of ${items.length} selected`} + + )} */} +
+ ); + }, [selectedKeys, items.length, page, pages, hasSearchFilter]); + + const classNames = React.useMemo( + () => ({ + wrapper: ["max-h-[382px]", "max-w-3xl"], + th: ["bg-transparent", "text-default-500", "border-b", "border-divider"], + td: [ + // changing the rows border radius + // first + "group-data-[first=true]:first:before:rounded-none", + "group-data-[first=true]:last:before:rounded-none", + // middle + "group-data-[middle=true]:before:rounded-none", + // last + "group-data-[last=true]:first:before:rounded-none", + "group-data-[last=true]:last:before:rounded-none", + ], + }), + [], + ); + + return ( + + + {(column) => ( + + {column.name} + + )} + + + {(item) => { + return ( + + {(columnKey) => ( + // @ts-expect-error: Disable type checking for this line + {renderCell(item, columnKey)} + )} + + ); + }} + +
+ ); +}; diff --git a/utils/capitalize.ts b/utils/capitalize.ts new file mode 100644 index 0000000000..9996d36f81 --- /dev/null +++ b/utils/capitalize.ts @@ -0,0 +1,3 @@ +export function capitalize(str: string): string { + return str.charAt(0).toUpperCase() + str.slice(1); +} From e6d84cb2455484044b7c891ce45371e82771ae06 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Fri, 19 Jul 2024 23:50:19 +0200 Subject: [PATCH 049/411] feat: replace the favIcon (#14) --- public/favicon.ico | Bin 25931 -> 15406 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/public/favicon.ico b/public/favicon.ico index 718d6fea4835ec2d246af9800eddb7ffb276240c..d695dcbe33df10dcf18a34c346da779907837c41 100644 GIT binary patch literal 15406 zcmeI2Tc{RQ7>3t&TXc|8qC|q)9S8%d9UVxl+|WhPN$M)QNh-<^uY<4xiLx+_B8ZYr z6h%SVOK6se<*XeuLH*40cS@Rn+ zX2#9@`G(_!Sv}X7%ZxD-6Gwh8USP~LX_J$)`R5t){;9?+rVLf6aU`8XifyFO?-qBZ zRIZDkgt(z6)*zS3C9k2d%nQ8#=0; z^-#BZeY;Nm{4kZ5)f&jDul8Px)PInCM&wb}a&GI`7pY$#KgUu2kk!Utk^0Y&f3wBY zR$b}X4V4|w$p>KTi=9u;R^|>gTQlqG+Uho)16wldDh4Y-_9(=3R{1|t_6@iMIx%%% z0;WLoQv*AbIzI)_H7RLl(RdXf*YV?$&S`t_Kg8cP?y9t5{FS`{bMC*Zk#b9oYx%A9 zft~?PEY<&QH2x=4l)D4;TuZT-|NS&_0DS&j z>59kWkfI^x-_?Ffn$~FdLCi||m&V`VqKJT!{-t3Xj0H55_OG!Jia}}rioruI`aeV6 z>jK+`R9(`Q}y=Z!l!Kk7d8 zSzG^_<2He>?@IsD=bZGIe~qmd18997y7uzF6?}FD86oXmaO4P9K36~uXstU2U0R#?pNCd54z|} zQ~jsm{{{F1TzY+JdT-Qw#-j)6ATM8Cc%Xdg;3_34bI>-~5)tcJPJ)N=J}|8F~_ zenW}%Lov|4&MDBua`j73*Lw%Q(&l4ua)S;u(I1Oxll%i{*I3H6zUWtVxLvoO_{f`nAV?6Qn*H`lerV!!$@&iuFyu#?WWr+BbFer;qv%A#{aHtRpQ~zn)vK z!u<(6K$(|d22$2Y_Pnd-L5k(-&)q-NcY@`hZ=bfl*!i{;@sug8Uoz%^{%4x=+@7I) zij6|QbZAc3|4CwTuky8~NU>4rKM65=pwbsRYF(FNqtdT+f##K%?kZo`D#b>jU+YS} zFI3u6jyqDsQzlpc&(v81dhbnPDdqHDto3Dz?yAholuxl-{hG)1{`W;9-%u`R+(^IX zskxp667UUTw$a1QuPdFr3`y-MrJZGStOt^~~~F4O;p-U6+Oa@#IhUSsAR(EaMu zuK!@a0(~0q8ZY}`3oHVkDehW(9E6f_Ol@0ZSL+Vxz6SI?AY_k_ssG%iwqK&$e*EtQ Utx;}=^T9XAkTuv4fo>x3FNFNOF8}}l literal 25931 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 LrJugUA?W`A8`#=m From b2d3f492ec2ffdca92459d8d25ac46b278fa6467 Mon Sep 17 00:00:00 2001 From: Pepe Fagoaga Date: Mon, 22 Jul 2024 15:25:37 +0200 Subject: [PATCH 050/411] chore(CODEOWNERS): Update with new team (#15) --- .github/CODEOWNERS | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000000..2ad99bd49a --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,5 @@ +* @prowler-cloud/ui + +# To protect a repository fully against unauthorized changes, you also need to define an owner for the CODEOWNERS file itself. +# https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners#codeowners-and-branch-protection +/.github/ @prowler-cloud/ui From 6ab0a42f6749728b3acdaaf0c148796364c4c2ac Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Tue, 23 Jul 2024 13:14:34 +0200 Subject: [PATCH 051/411] feat: account component is ready to use it --- components/icons/Icons.tsx | 69 ++++++++++++++++ components/icons/index.ts | 3 + components/icons/providers/AwsProvider.tsx | 42 ++++++++++ components/icons/providers/AzureProvider.tsx | 80 +++++++++++++++++++ .../icons/providers/GoogleCloudProvider.tsx | 42 ++++++++++ components/index.ts | 1 + components/providers/AccountInfo.tsx | 54 +++++++++++++ types/components.ts | 10 +++ types/index.ts | 6 +- 9 files changed, 302 insertions(+), 5 deletions(-) create mode 100644 components/icons/providers/AwsProvider.tsx create mode 100644 components/icons/providers/AzureProvider.tsx create mode 100644 components/icons/providers/GoogleCloudProvider.tsx create mode 100644 components/providers/AccountInfo.tsx create mode 100644 types/components.ts diff --git a/components/icons/Icons.tsx b/components/icons/Icons.tsx index 0f0cd7c60d..9b79a7c35d 100644 --- a/components/icons/Icons.tsx +++ b/components/icons/Icons.tsx @@ -201,3 +201,72 @@ export const VerticalDotsIcon: React.FC = ({ ); +export const WifiIcon: React.FC = ({ + size = 24, + width, + height, + ...props +}) => ( + +); + +export const WifiOffIcon: React.FC = ({ + size = 24, + width, + height, + ...props +}) => ( + +); diff --git a/components/icons/index.ts b/components/icons/index.ts index e733eda3ef..3929e06cd5 100644 --- a/components/icons/index.ts +++ b/components/icons/index.ts @@ -1,2 +1,5 @@ export * from "./Icons"; +export * from "./providers/AwsProvider"; +export * from "./providers/AzureProvider"; +export * from "./providers/GoogleCloudProvider"; export * from "./prowler/ProwlerIcons"; diff --git a/components/icons/providers/AwsProvider.tsx b/components/icons/providers/AwsProvider.tsx new file mode 100644 index 0000000000..48cdfe377d --- /dev/null +++ b/components/icons/providers/AwsProvider.tsx @@ -0,0 +1,42 @@ +import * as React from "react"; + +import { IconSvgProps } from "@/types"; + +export const AwsProvider: React.FC = ({ + size, + width, + height, + ...props +}) => ( + +); diff --git a/components/icons/providers/AzureProvider.tsx b/components/icons/providers/AzureProvider.tsx new file mode 100644 index 0000000000..1f20189bbf --- /dev/null +++ b/components/icons/providers/AzureProvider.tsx @@ -0,0 +1,80 @@ +import * as React from "react"; + +import { IconSvgProps } from "@/types"; + +export const AzureProvider: React.FC = ({ + size, + width, + height, + ...props +}) => ( + +); diff --git a/components/icons/providers/GoogleCloudProvider.tsx b/components/icons/providers/GoogleCloudProvider.tsx new file mode 100644 index 0000000000..0fa5bde7ae --- /dev/null +++ b/components/icons/providers/GoogleCloudProvider.tsx @@ -0,0 +1,42 @@ +import * as React from "react"; + +import { IconSvgProps } from "@/types"; + +export const GoogleCloudProvider: React.FC = ({ + size, + width, + height, + ...props +}) => ( + +); diff --git a/components/index.ts b/components/index.ts index e0bd724528..781f3a8b73 100644 --- a/components/index.ts +++ b/components/index.ts @@ -1,3 +1,4 @@ +export * from "./providers/AccountInfo"; export * from "./ui/header/Header"; export * from "./ui/sidebar"; export * from "./ui/table/CustomTable"; diff --git a/components/providers/AccountInfo.tsx b/components/providers/AccountInfo.tsx new file mode 100644 index 0000000000..99ad95a3b9 --- /dev/null +++ b/components/providers/AccountInfo.tsx @@ -0,0 +1,54 @@ +import React from "react"; + +import { + AwsProvider, + AzureProvider, + GoogleCloudProvider, + WifiIcon, + WifiOffIcon, +} from "../icons"; + +interface AccountInfoProps { + connected: boolean; + provider: "aws" | "azure" | "gcp"; + accountName: string; + accountId: string; +} + +export const AccountInfo: React.FC = ({ + connected, + provider, + accountName, + accountId, +}) => { + const getIcon = () => { + return connected ? : ; + }; + + const getProviderLogo = () => { + switch (provider) { + case "aws": + return ; + case "azure": + return ; + + case "gcp": + return ; + default: + return null; + } + }; + + return ( +
+
+
{getIcon()}
+
{getProviderLogo()}
+
+ {accountName} + {accountId} +
+
+
+ ); +}; diff --git a/types/components.ts b/types/components.ts new file mode 100644 index 0000000000..c9b078e314 --- /dev/null +++ b/types/components.ts @@ -0,0 +1,10 @@ +import { SVGProps } from "react"; + +export type IconSvgProps = SVGProps & { + size?: number; +}; + +export type IconProps = { + icon: React.FC; + style?: React.CSSProperties; +}; diff --git a/types/index.ts b/types/index.ts index cece4a4775..40b494c5f8 100644 --- a/types/index.ts +++ b/types/index.ts @@ -1,5 +1 @@ -import { SVGProps } from "react"; - -export type IconSvgProps = SVGProps & { - size?: number; -}; +export * from "./components"; From 0590c00c9b1d61c159c3db290d0284c2a5ba064a Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Tue, 23 Jul 2024 15:23:39 +0200 Subject: [PATCH 052/411] feat: add date-fns library to the project --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 3756a3d8e8..11352e1295 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "@react-aria/ssr": "3.9.4", "@react-aria/visually-hidden": "3.8.12", "clsx": "2.1.1", + "date-fns": "^3.6.0", "framer-motion": "~11.1.1", "intl-messageformat": "^10.5.0", "next": "14.2.3", From db30c0253d78795d538471d08f8ccf3740463eb1 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Tue, 23 Jul 2024 15:37:10 +0200 Subject: [PATCH 053/411] feat: ScanStatus component is ready to use it --- components/index.ts | 1 + components/providers/ScanStatus.tsx | 28 ++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 components/providers/ScanStatus.tsx diff --git a/components/index.ts b/components/index.ts index 781f3a8b73..c9c0537492 100644 --- a/components/index.ts +++ b/components/index.ts @@ -1,4 +1,5 @@ export * from "./providers/AccountInfo"; +export * from "./providers/ScanStatus"; export * from "./ui/header/Header"; export * from "./ui/sidebar"; export * from "./ui/table/CustomTable"; diff --git a/components/providers/ScanStatus.tsx b/components/providers/ScanStatus.tsx new file mode 100644 index 0000000000..e0d826edbd --- /dev/null +++ b/components/providers/ScanStatus.tsx @@ -0,0 +1,28 @@ +import { formatDuration, intervalToDuration, parseISO } from "date-fns"; +import React from "react"; + +interface ScanStatusProps { + status: string; + createdAt: string; // ISO string +} + +export const ScanStatus: React.FC = ({ + status, + createdAt, +}) => { + const duration = intervalToDuration({ + start: parseISO(createdAt), + end: new Date(), + }); + + const formattedDuration = formatDuration(duration, { delimiter: ", " }); + + return ( +
+
+ {status} + {formattedDuration} +
+
+ ); +}; From 1c5859d93cd7904bc18d7e4be2c431ae92a5e0bf Mon Sep 17 00:00:00 2001 From: Sophia Dao Date: Tue, 23 Jul 2024 22:40:54 -0500 Subject: [PATCH 054/411] feat(modal): Use server to pass event handler --- app/(prowler)/providers/page.tsx | 18 +++++++++++- components/index.ts | 2 +- components/ui/modal/Modal.tsx | 14 ++++----- components/ui/modal/ModalWrap.tsx | 48 +++++++++++++++++++++++++++++++ components/ui/modal/index.ts | 2 ++ 5 files changed, 75 insertions(+), 9 deletions(-) create mode 100644 components/ui/modal/ModalWrap.tsx create mode 100644 components/ui/modal/index.ts diff --git a/app/(prowler)/providers/page.tsx b/app/(prowler)/providers/page.tsx index 687eca0ed6..7e876da05d 100644 --- a/app/(prowler)/providers/page.tsx +++ b/app/(prowler)/providers/page.tsx @@ -1,7 +1,7 @@ import { Spacer } from "@nextui-org/react"; import React from "react"; -import { CustomTable, Header } from "@/components"; +import { CustomTable, Header, ModalWrap } from "@/components"; const visibleColumns: string[] = [ "account", @@ -15,6 +15,11 @@ const visibleColumns: string[] = [ ]; export default function Providers() { + const onSave = async () => { + "use server"; + // event we want to pass down, ex. console.log("### hello"); + }; + return ( <>
@@ -25,6 +30,17 @@ export default function Providers() { selectionMode={"none"} /> + +

Modal body content

+ + } + actionText="Save" + onAction={onSave} + triggerText="Open Modal" + /> ); } diff --git a/components/index.ts b/components/index.ts index cec3a70f4b..8bc5c4d608 100644 --- a/components/index.ts +++ b/components/index.ts @@ -1,4 +1,4 @@ export * from "./ui/header/Header"; -export * from "./ui/modal/Modal"; +export * from "./ui/modal"; export * from "./ui/sidebar"; export * from "./ui/table/CustomTable"; diff --git a/components/ui/modal/Modal.tsx b/components/ui/modal/Modal.tsx index 1344ec9ecf..eabfa171c8 100644 --- a/components/ui/modal/Modal.tsx +++ b/components/ui/modal/Modal.tsx @@ -10,12 +10,12 @@ import React from "react"; interface ModalProps { title: string; - isOpen: boolean; - onOpenChange: (isOpen: boolean) => void; body: React.ReactNode; - onCloseText?: string; + closeText?: string; actionText?: string; onAction?: () => void; + isOpen: boolean; + onOpenChange: (isOpen: boolean) => void; isDismissable?: boolean; } @@ -24,10 +24,10 @@ export const Modal: React.FC = ({ isOpen, onOpenChange, body, - onCloseText = "Close", - actionText = "Save", + closeText, + actionText, onAction, - isDismissable = true, + isDismissable, }) => { const hasActionButton = actionText && onAction; @@ -48,7 +48,7 @@ export const Modal: React.FC = ({ {body} {hasActionButton && ( + + + ); +}; diff --git a/components/ui/modal/index.ts b/components/ui/modal/index.ts new file mode 100644 index 0000000000..c33df2971c --- /dev/null +++ b/components/ui/modal/index.ts @@ -0,0 +1,2 @@ +export * from "./Modal"; +export * from "./ModalWrap"; From 044c8dbb3a25129464d7a9826480c1aedf28bce1 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Wed, 24 Jul 2024 09:39:34 +0200 Subject: [PATCH 055/411] feat: DateWithTime component is ready to use it --- components/index.ts | 1 + components/providers/AccountInfo.tsx | 2 +- components/providers/DateWithTime.tsx | 27 +++++++++++++++++++++++++++ components/providers/ScanStatus.tsx | 4 ++-- 4 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 components/providers/DateWithTime.tsx diff --git a/components/index.ts b/components/index.ts index c9c0537492..a1ef4db7b1 100644 --- a/components/index.ts +++ b/components/index.ts @@ -1,4 +1,5 @@ export * from "./providers/AccountInfo"; +export * from "./providers/DateWithTime"; export * from "./providers/ScanStatus"; export * from "./ui/header/Header"; export * from "./ui/sidebar"; diff --git a/components/providers/AccountInfo.tsx b/components/providers/AccountInfo.tsx index 99ad95a3b9..7cab849d54 100644 --- a/components/providers/AccountInfo.tsx +++ b/components/providers/AccountInfo.tsx @@ -40,7 +40,7 @@ export const AccountInfo: React.FC = ({ }; return ( -
+
{getIcon()}
{getProviderLogo()}
diff --git a/components/providers/DateWithTime.tsx b/components/providers/DateWithTime.tsx new file mode 100644 index 0000000000..9895483e31 --- /dev/null +++ b/components/providers/DateWithTime.tsx @@ -0,0 +1,27 @@ +import { format, parseISO } from "date-fns"; +import React from "react"; + +interface DateWithTimeProps { + dateTime: string; // e.g., "2024-07-17T09:55:14.191475Z" + showTime?: boolean; +} + +export const DateWithTime: React.FC = ({ + dateTime, + showTime = true, +}) => { + const date = parseISO(dateTime); + const formattedDate = format(date, "MMM dd, yyyy"); + const formattedTime = format(date, "p 'UTC'"); + + return ( +
+
+ {formattedDate} + {showTime && ( + {formattedTime} + )} +
+
+ ); +}; diff --git a/components/providers/ScanStatus.tsx b/components/providers/ScanStatus.tsx index e0d826edbd..d181cbc187 100644 --- a/components/providers/ScanStatus.tsx +++ b/components/providers/ScanStatus.tsx @@ -18,8 +18,8 @@ export const ScanStatus: React.FC = ({ const formattedDuration = formatDuration(duration, { delimiter: ", " }); return ( -
-
+
+
{status} {formattedDuration}
From dd2c92d80561f09ff17cedc0c32ccb99b48c68b3 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Wed, 24 Jul 2024 16:08:15 +0200 Subject: [PATCH 056/411] feat: account component is ready to use it (#16) --- components/icons/Icons.tsx | 69 ++++++++++++++++ components/icons/index.ts | 3 + components/icons/providers/AwsProvider.tsx | 42 ++++++++++ components/icons/providers/AzureProvider.tsx | 80 +++++++++++++++++++ .../icons/providers/GoogleCloudProvider.tsx | 42 ++++++++++ components/index.ts | 1 + components/providers/AccountInfo.tsx | 54 +++++++++++++ types/components.ts | 10 +++ types/index.ts | 6 +- 9 files changed, 302 insertions(+), 5 deletions(-) create mode 100644 components/icons/providers/AwsProvider.tsx create mode 100644 components/icons/providers/AzureProvider.tsx create mode 100644 components/icons/providers/GoogleCloudProvider.tsx create mode 100644 components/providers/AccountInfo.tsx create mode 100644 types/components.ts diff --git a/components/icons/Icons.tsx b/components/icons/Icons.tsx index 0f0cd7c60d..9b79a7c35d 100644 --- a/components/icons/Icons.tsx +++ b/components/icons/Icons.tsx @@ -201,3 +201,72 @@ export const VerticalDotsIcon: React.FC = ({ ); +export const WifiIcon: React.FC = ({ + size = 24, + width, + height, + ...props +}) => ( + +); + +export const WifiOffIcon: React.FC = ({ + size = 24, + width, + height, + ...props +}) => ( + +); diff --git a/components/icons/index.ts b/components/icons/index.ts index e733eda3ef..3929e06cd5 100644 --- a/components/icons/index.ts +++ b/components/icons/index.ts @@ -1,2 +1,5 @@ export * from "./Icons"; +export * from "./providers/AwsProvider"; +export * from "./providers/AzureProvider"; +export * from "./providers/GoogleCloudProvider"; export * from "./prowler/ProwlerIcons"; diff --git a/components/icons/providers/AwsProvider.tsx b/components/icons/providers/AwsProvider.tsx new file mode 100644 index 0000000000..48cdfe377d --- /dev/null +++ b/components/icons/providers/AwsProvider.tsx @@ -0,0 +1,42 @@ +import * as React from "react"; + +import { IconSvgProps } from "@/types"; + +export const AwsProvider: React.FC = ({ + size, + width, + height, + ...props +}) => ( + +); diff --git a/components/icons/providers/AzureProvider.tsx b/components/icons/providers/AzureProvider.tsx new file mode 100644 index 0000000000..1f20189bbf --- /dev/null +++ b/components/icons/providers/AzureProvider.tsx @@ -0,0 +1,80 @@ +import * as React from "react"; + +import { IconSvgProps } from "@/types"; + +export const AzureProvider: React.FC = ({ + size, + width, + height, + ...props +}) => ( + +); diff --git a/components/icons/providers/GoogleCloudProvider.tsx b/components/icons/providers/GoogleCloudProvider.tsx new file mode 100644 index 0000000000..0fa5bde7ae --- /dev/null +++ b/components/icons/providers/GoogleCloudProvider.tsx @@ -0,0 +1,42 @@ +import * as React from "react"; + +import { IconSvgProps } from "@/types"; + +export const GoogleCloudProvider: React.FC = ({ + size, + width, + height, + ...props +}) => ( + +); diff --git a/components/index.ts b/components/index.ts index e0bd724528..781f3a8b73 100644 --- a/components/index.ts +++ b/components/index.ts @@ -1,3 +1,4 @@ +export * from "./providers/AccountInfo"; export * from "./ui/header/Header"; export * from "./ui/sidebar"; export * from "./ui/table/CustomTable"; diff --git a/components/providers/AccountInfo.tsx b/components/providers/AccountInfo.tsx new file mode 100644 index 0000000000..99ad95a3b9 --- /dev/null +++ b/components/providers/AccountInfo.tsx @@ -0,0 +1,54 @@ +import React from "react"; + +import { + AwsProvider, + AzureProvider, + GoogleCloudProvider, + WifiIcon, + WifiOffIcon, +} from "../icons"; + +interface AccountInfoProps { + connected: boolean; + provider: "aws" | "azure" | "gcp"; + accountName: string; + accountId: string; +} + +export const AccountInfo: React.FC = ({ + connected, + provider, + accountName, + accountId, +}) => { + const getIcon = () => { + return connected ? : ; + }; + + const getProviderLogo = () => { + switch (provider) { + case "aws": + return ; + case "azure": + return ; + + case "gcp": + return ; + default: + return null; + } + }; + + return ( +
+
+
{getIcon()}
+
{getProviderLogo()}
+
+ {accountName} + {accountId} +
+
+
+ ); +}; diff --git a/types/components.ts b/types/components.ts new file mode 100644 index 0000000000..c9b078e314 --- /dev/null +++ b/types/components.ts @@ -0,0 +1,10 @@ +import { SVGProps } from "react"; + +export type IconSvgProps = SVGProps & { + size?: number; +}; + +export type IconProps = { + icon: React.FC; + style?: React.CSSProperties; +}; diff --git a/types/index.ts b/types/index.ts index cece4a4775..40b494c5f8 100644 --- a/types/index.ts +++ b/types/index.ts @@ -1,5 +1 @@ -import { SVGProps } from "react"; - -export type IconSvgProps = SVGProps & { - size?: number; -}; +export * from "./components"; From 57f5fd51e6055f18792678847771c4001a08d337 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Thu, 25 Jul 2024 10:30:28 +0200 Subject: [PATCH 057/411] fix: add suppressHydrationWarning to resolve console errors --- app/(prowler)/layout.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/app/(prowler)/layout.tsx b/app/(prowler)/layout.tsx index 9c9b7d2a93..149a706445 100644 --- a/app/(prowler)/layout.tsx +++ b/app/(prowler)/layout.tsx @@ -37,6 +37,7 @@ export default function RootLayout({ Date: Thu, 25 Jul 2024 09:01:49 -0500 Subject: [PATCH 058/411] feat(modal): Code review feedback --- app/(prowler)/providers/page.tsx | 8 +++---- components/ui/modal/Modal.tsx | 40 +++++++++++++++---------------- components/ui/modal/ModalWrap.tsx | 34 +++++++++++++------------- 3 files changed, 41 insertions(+), 41 deletions(-) diff --git a/app/(prowler)/providers/page.tsx b/app/(prowler)/providers/page.tsx index 7e876da05d..d3cd8c1611 100644 --- a/app/(prowler)/providers/page.tsx +++ b/app/(prowler)/providers/page.tsx @@ -31,15 +31,15 @@ export default function Providers() { />

Modal body content

} - actionText="Save" + actionButtonLabel="Save" onAction={onSave} - triggerText="Open Modal" + openButtonLabel="Open Modal" /> ); diff --git a/components/ui/modal/Modal.tsx b/components/ui/modal/Modal.tsx index eabfa171c8..ca7754a96e 100644 --- a/components/ui/modal/Modal.tsx +++ b/components/ui/modal/Modal.tsx @@ -9,28 +9,28 @@ import { import React from "react"; interface ModalProps { - title: string; - body: React.ReactNode; - closeText?: string; - actionText?: string; - onAction?: () => void; + modalTitle: string; + modalBody: React.ReactNode; + closeButtonLabel?: string; + actionButtonLabel?: string; + onAction: () => void; isOpen: boolean; onOpenChange: (isOpen: boolean) => void; isDismissable?: boolean; + isKeyboardDismissDisabled?: boolean; + hideCloseButton?: boolean; } export const Modal: React.FC = ({ - title, + modalTitle, + modalBody, + closeButtonLabel, + actionButtonLabel, + onAction, isOpen, onOpenChange, - body, - closeText, - actionText, - onAction, isDismissable, }) => { - const hasActionButton = actionText && onAction; - return ( <> = ({ {(onClose) => ( <> - {title} - {body} + + {modalTitle} + + {modalBody} + - {hasActionButton && ( - - )} )} diff --git a/components/ui/modal/ModalWrap.tsx b/components/ui/modal/ModalWrap.tsx index d2eca3491f..42318210bf 100644 --- a/components/ui/modal/ModalWrap.tsx +++ b/components/ui/modal/ModalWrap.tsx @@ -6,38 +6,38 @@ import React from "react"; import { Modal } from "@/components"; interface ModalWrapProps { - title: string; - body: React.ReactNode; - closeText?: string; - actionText?: string; - onAction?: () => void; - triggerText?: string; + modalTitle: string; + modalBody: React.ReactNode; + closeButtonLabel?: string; + actionButtonLabel?: string; + onAction: () => void; + openButtonLabel?: string; isDismissable?: boolean; } export const ModalWrap: React.FC = ({ - title, - body, - closeText = "Close", - actionText = "Save", + modalTitle, + modalBody, + closeButtonLabel = "Close", + actionButtonLabel = "Save", onAction, + openButtonLabel = "Open", isDismissable = true, - triggerText = "Open", }) => { const { isOpen, onOpen, onClose, onOpenChange } = useDisclosure(); const closeOnAction = () => { - onAction && onAction(); + onAction?.(); onClose(); }; return ( <> - + Date: Fri, 26 Jul 2024 09:32:48 +0200 Subject: [PATCH 059/411] chore: add server-only library --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 11352e1295..a4aa735b6d 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "next-themes": "^0.2.1", "react": "18.3.1", "react-dom": "18.3.1", + "server-only": "^0.0.1", "swr": "^2.2.5" }, "devDependencies": { From 0ee60efaa7af45a30d6af55a3c24affb082d7d25 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Fri, 26 Jul 2024 18:01:50 +0200 Subject: [PATCH 060/411] WIP: Mock API for providers and start rendering data --- app/api/providers/route.ts | 10 ++++ app/data/dataProvider.ts | 8 +++ app/data/index.ts | 1 + dataProviders.json | 99 ++++++++++++++++++++++++++++++++++++++ utils/fetcher.tsx | 1 - utils/index.ts | 1 + 6 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 app/api/providers/route.ts create mode 100644 app/data/dataProvider.ts create mode 100644 app/data/index.ts create mode 100644 dataProviders.json delete mode 100644 utils/fetcher.tsx create mode 100644 utils/index.ts diff --git a/app/api/providers/route.ts b/app/api/providers/route.ts new file mode 100644 index 0000000000..ef9b5995e2 --- /dev/null +++ b/app/api/providers/route.ts @@ -0,0 +1,10 @@ +import { NextResponse } from "next/server"; + +import data from "../../../dataProviders.json"; + +export async function GET() { + // Simulate fetching data with a delay + await new Promise((resolve) => setTimeout(resolve, 2000)); + + return NextResponse.json({ providers: data }); +} diff --git a/app/data/dataProvider.ts b/app/data/dataProvider.ts new file mode 100644 index 0000000000..429f18f4cc --- /dev/null +++ b/app/data/dataProvider.ts @@ -0,0 +1,8 @@ +import "server-only"; + +export default async function getProvider() { + const res = await fetch("http://localhost:3000/api/providers"); + const product = await res.json(); + + return product; +} diff --git a/app/data/index.ts b/app/data/index.ts new file mode 100644 index 0000000000..138e0c8df1 --- /dev/null +++ b/app/data/index.ts @@ -0,0 +1 @@ +export * from "./dataProvider"; diff --git a/dataProviders.json b/dataProviders.json new file mode 100644 index 0000000000..6aec7c9584 --- /dev/null +++ b/dataProviders.json @@ -0,0 +1,99 @@ +{ + "links": { + "first": "https://api.prowler.com/api/v1/providers?page%5Bnumber%5D=1", + "last": "https://api.prowler.com/api/v1/providers?page%5Bnumber%5D=1", + "next": null, + "prev": null + }, + "data": [ + { + "id": "5fd8f121-269e-4715-84cf-f92373f15dfa", + "type": "providers", + "attributes": { + "provider": "aws", + "provider_id": "1234567890", + "alias": "mock_aws_connected", + "connection": { + "connected": true, + "last_checked_at": "2024-07-17T09:55:14.191475Z" + }, + "scanner_args": { + "only_logs": true, + "excluded_checks": [ + "awslambda_function_no_secrets_in_code", + "cloudwatch_log_group_no_secrets_in_logs" + ], + "aws_retries_max_attempts": 5 + }, + "inserted_at": "2024-07-17T09:55:14.191475Z", + "updated_at": "2024-07-17T09:55:14.191475Z", + "created_by": { + "object": "user", + "id": "eea048ab-7cb3-47eb-9e5e-dce591ade41f" + } + } + }, + { + "id": "16aaeb4e-d3cd-4bb6-86f8-6c39cf93821e", + "type": "providers", + "attributes": { + "provider": "aws", + "provider_id": "1234567891", + "alias": "mock_aws_not_connected", + "connection": { + "connected": false, + "last_checked_at": "2024-07-17T09:55:18.987425Z" + }, + "scanner_args": { + "only_logs": true, + "excluded_checks": [ + "awslambda_function_no_secrets_in_code", + "cloudwatch_log_group_no_secrets_in_logs" + ], + "aws_retries_max_attempts": 5 + }, + "inserted_at": "2024-07-17T09:50:18.987425Z", + "updated_at": "2024-07-17T09:55:18.987425Z", + "created_by": { + "object": "user", + "id": "a8f5e964-5964-4aaf-9176-844e2c3b0716" + } + } + }, + { + "id": "63f16b03-7849-4054-b40b-300e331f46f0", + "type": "providers", + "attributes": { + "provider": "gcp", + "provider_id": "1234567895", + "alias": "mock_gcp", + "connection": { + "connected": true, + "last_checked_at": "2024-07-17T09:55:18.987425Z" + }, + "scanner_args": { + "only_logs": true, + "excluded_checks": [ + "apikeys_key_exists", + "cloudsql_instance_public_ip" + ], + "excluded_services": ["kms"] + }, + "inserted_at": "2024-07-17T09:50:18.987425Z", + "updated_at": "2024-07-17T09:55:18.987425Z", + "created_by": { + "object": "user", + "id": "eea048ab-7cb3-47eb-9e5e-dce591ade41f" + } + } + } + ], + "meta": { + "pagination": { + "page": 1, + "pages": 1, + "count": 3 + }, + "version": "v1" + } +} diff --git a/utils/fetcher.tsx b/utils/fetcher.tsx deleted file mode 100644 index 50b047c9ad..0000000000 --- a/utils/fetcher.tsx +++ /dev/null @@ -1 +0,0 @@ -export const fetcher = (url: string) => fetch(url).then((res) => res.json()); diff --git a/utils/index.ts b/utils/index.ts new file mode 100644 index 0000000000..d6820eec0c --- /dev/null +++ b/utils/index.ts @@ -0,0 +1 @@ +export * from "./capitalize"; From b8de7134978dbd081fa5d1e970d94507b7c92962 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Sun, 28 Jul 2024 16:35:45 +0200 Subject: [PATCH 061/411] chore: relocate utils folder to proper directory --- lib/actions/index.ts | 1 + lib/actions/provider.actions.ts | 13 ++++++ lib/index.ts | 1 + lib/utils.ts | 81 +++++++++++++++++++++++++++++++++ utils/capitalize.ts | 3 -- utils/index.ts | 1 - 6 files changed, 96 insertions(+), 4 deletions(-) create mode 100644 lib/actions/index.ts create mode 100644 lib/actions/provider.actions.ts create mode 100644 lib/index.ts create mode 100644 lib/utils.ts delete mode 100644 utils/capitalize.ts delete mode 100644 utils/index.ts diff --git a/lib/actions/index.ts b/lib/actions/index.ts new file mode 100644 index 0000000000..67b97f1e4a --- /dev/null +++ b/lib/actions/index.ts @@ -0,0 +1 @@ +export * from "./provider.actions"; diff --git a/lib/actions/provider.actions.ts b/lib/actions/provider.actions.ts new file mode 100644 index 0000000000..02cb2578e9 --- /dev/null +++ b/lib/actions/provider.actions.ts @@ -0,0 +1,13 @@ +import "server-only"; + +import { parseStringify } from "../utils"; + +export const getProvider = async () => { + try { + const providers = await fetch("http://localhost:3000/api/providers"); + const data = await providers.json(); + return parseStringify(data); + } catch (error) { + console.log(error); + } +}; diff --git a/lib/index.ts b/lib/index.ts new file mode 100644 index 0000000000..178cd64f81 --- /dev/null +++ b/lib/index.ts @@ -0,0 +1 @@ +export * from "./utils"; diff --git a/lib/utils.ts b/lib/utils.ts new file mode 100644 index 0000000000..261f2865a8 --- /dev/null +++ b/lib/utils.ts @@ -0,0 +1,81 @@ +import { type ClassValue, clsx } from "clsx"; +import { twMerge } from "tailwind-merge"; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} + +export function capitalize(str: string): string { + return str.charAt(0).toUpperCase() + str.slice(1); +} + +export const parseStringify = (value: any) => JSON.parse(JSON.stringify(value)); + +export const convertFileToUrl = (file: File) => URL.createObjectURL(file); + +// FORMAT DATE TIME +export const formatDateTime = (dateString: Date | string) => { + const dateTimeOptions: Intl.DateTimeFormatOptions = { + // weekday: "short", // abbreviated weekday name (e.g., 'Mon') + month: "short", // abbreviated month name (e.g., 'Oct') + day: "numeric", // numeric day of the month (e.g., '25') + year: "numeric", // numeric year (e.g., '2023') + hour: "numeric", // numeric hour (e.g., '8') + minute: "numeric", // numeric minute (e.g., '30') + hour12: true, // use 12-hour clock (true) or 24-hour clock (false) + }; + + const dateDayOptions: Intl.DateTimeFormatOptions = { + weekday: "short", // abbreviated weekday name (e.g., 'Mon') + year: "numeric", // numeric year (e.g., '2023') + month: "2-digit", // abbreviated month name (e.g., 'Oct') + day: "2-digit", // numeric day of the month (e.g., '25') + }; + + const dateOptions: Intl.DateTimeFormatOptions = { + month: "short", // abbreviated month name (e.g., 'Oct') + year: "numeric", // numeric year (e.g., '2023') + day: "numeric", // numeric day of the month (e.g., '25') + }; + + const timeOptions: Intl.DateTimeFormatOptions = { + hour: "numeric", // numeric hour (e.g., '8') + minute: "numeric", // numeric minute (e.g., '30') + hour12: true, // use 12-hour clock (true) or 24-hour clock (false) + }; + + const formattedDateTime: string = new Date(dateString).toLocaleString( + "en-US", + dateTimeOptions, + ); + + const formattedDateDay: string = new Date(dateString).toLocaleString( + "en-US", + dateDayOptions, + ); + + const formattedDate: string = new Date(dateString).toLocaleString( + "en-US", + dateOptions, + ); + + const formattedTime: string = new Date(dateString).toLocaleString( + "en-US", + timeOptions, + ); + + return { + dateTime: formattedDateTime, + dateDay: formattedDateDay, + dateOnly: formattedDate, + timeOnly: formattedTime, + }; +}; + +export function encryptKey(passkey: string) { + return btoa(passkey); +} + +export function decryptKey(passkey: string) { + return atob(passkey); +} diff --git a/utils/capitalize.ts b/utils/capitalize.ts deleted file mode 100644 index 9996d36f81..0000000000 --- a/utils/capitalize.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function capitalize(str: string): string { - return str.charAt(0).toUpperCase() + str.slice(1); -} diff --git a/utils/index.ts b/utils/index.ts deleted file mode 100644 index d6820eec0c..0000000000 --- a/utils/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./capitalize"; From 54b3fc3ae643d994bdb0f231e6831ac5943a68b5 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Sun, 28 Jul 2024 16:40:29 +0200 Subject: [PATCH 062/411] chore: install shadcn for tables, adding sttings page --- app/(prowler)/layout.tsx | 4 ++-- app/(prowler)/settings/page.tsx | 12 ++++++++++++ app/data/dataProvider.ts | 8 -------- app/data/index.ts | 1 - config/fonts.ts | 5 ++++- dataProviders.json | 2 +- package.json | 9 +++++++-- types/components.ts | 25 +++++++++++++++++++++++++ 8 files changed, 51 insertions(+), 15 deletions(-) create mode 100644 app/(prowler)/settings/page.tsx delete mode 100644 app/data/dataProvider.ts delete mode 100644 app/data/index.ts diff --git a/app/(prowler)/layout.tsx b/app/(prowler)/layout.tsx index 149a706445..13319c3540 100644 --- a/app/(prowler)/layout.tsx +++ b/app/(prowler)/layout.tsx @@ -1,12 +1,12 @@ import "@/styles/globals.css"; -import clsx from "clsx"; import { Metadata, Viewport } from "next"; import React from "react"; import { SidebarWrap } from "@/components"; import { fontSans } from "@/config/fonts"; import { siteConfig } from "@/config/site"; +import { cn } from "@/lib/utils"; import { Providers } from "../providers"; @@ -38,7 +38,7 @@ export default function RootLayout({ +
+ + + ); +} diff --git a/app/data/dataProvider.ts b/app/data/dataProvider.ts deleted file mode 100644 index 429f18f4cc..0000000000 --- a/app/data/dataProvider.ts +++ /dev/null @@ -1,8 +0,0 @@ -import "server-only"; - -export default async function getProvider() { - const res = await fetch("http://localhost:3000/api/providers"); - const product = await res.json(); - - return product; -} diff --git a/app/data/index.ts b/app/data/index.ts deleted file mode 100644 index 138e0c8df1..0000000000 --- a/app/data/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./dataProvider"; diff --git a/config/fonts.ts b/config/fonts.ts index 0e7d9c9422..16c934d5a6 100644 --- a/config/fonts.ts +++ b/config/fonts.ts @@ -1,4 +1,7 @@ -import { Fira_Code as FontMono, Inter as FontSans } from "next/font/google"; +import { + Fira_Code as FontMono, + Plus_Jakarta_Sans as FontSans, +} from "next/font/google"; export const fontSans = FontSans({ subsets: ["latin"], diff --git a/dataProviders.json b/dataProviders.json index 6aec7c9584..5199fe7228 100644 --- a/dataProviders.json +++ b/dataProviders.json @@ -37,7 +37,7 @@ "id": "16aaeb4e-d3cd-4bb6-86f8-6c39cf93821e", "type": "providers", "attributes": { - "provider": "aws", + "provider": "azure", "provider_id": "1234567891", "alias": "mock_aws_not_connected", "connection": { diff --git a/package.json b/package.json index c3aaef6df5..c0dd46c781 100644 --- a/package.json +++ b/package.json @@ -5,16 +5,21 @@ "@nextui-org/theme": "2.2.5", "@react-aria/ssr": "3.9.4", "@react-aria/visually-hidden": "3.8.12", - "clsx": "2.1.1", + "@tanstack/react-table": "^8.19.3", + "class-variance-authority": "^0.7.0", + "clsx": "^2.1.1", "date-fns": "^3.6.0", "framer-motion": "~11.1.1", "intl-messageformat": "^10.5.0", + "lucide-react": "^0.417.0", "next": "14.2.3", "next-themes": "^0.2.1", "react": "18.3.1", "react-dom": "18.3.1", "server-only": "^0.0.1", - "swr": "^2.2.5" + "swr": "^2.2.5", + "tailwind-merge": "^2.4.0", + "tailwindcss-animate": "^1.0.7" }, "devDependencies": { "@iconify/react": "^5.0.1", diff --git a/types/components.ts b/types/components.ts index c9b078e314..14bfe0b219 100644 --- a/types/components.ts +++ b/types/components.ts @@ -8,3 +8,28 @@ export type IconProps = { icon: React.FC; style?: React.CSSProperties; }; + +export interface ProviderProps { + id: string; + type: "providers"; + attributes: { + provider: "aws" | "azure" | "gcp"; + provider_id: string; + alias: string; + connection: { + connected: boolean; + last_checked_at: string; + }; + scanner_args: { + only_logs: boolean; + excluded_checks: string[]; + aws_retries_max_attempts: number; + }; + inserted_at: string; + updated_at: string; + created_by: { + object: string; + id: string; + }; + }; +} From 8ce28dd3119a4252e1b367a47b73061d5297b1e0 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Mon, 29 Jul 2024 11:13:39 +0200 Subject: [PATCH 063/411] refactor: improve sidebar display behavior --- components/ui/sidebar/SidebarWrap.tsx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/components/ui/sidebar/SidebarWrap.tsx b/components/ui/sidebar/SidebarWrap.tsx index f2212850bb..e68958a446 100644 --- a/components/ui/sidebar/SidebarWrap.tsx +++ b/components/ui/sidebar/SidebarWrap.tsx @@ -17,8 +17,9 @@ import { sectionItemsWithTeams } from "./SidebarItems"; import { UserAvatar } from "./UserAvatar"; export const SidebarWrap = () => { - const [isCollapsed, setIsCollapsed] = useState(false); - const [isLoaded, setIsLoaded] = useState(false); + const [isCollapsed, setIsCollapsed] = useState( + !!localStorage.getItem("isCollapsed") ?? true, + ); const pathname = usePathname(); const currentPath = pathname === "/" ? "overview" : pathname.split("/")?.[1]; @@ -32,7 +33,6 @@ export const SidebarWrap = () => { if (savedState !== null) { setIsCollapsed(JSON.parse(savedState)); } - setIsLoaded(true); }, []); const onToggle = useCallback(() => { @@ -43,8 +43,6 @@ export const SidebarWrap = () => { }); }, []); - if (!isLoaded) return null; - return (
Date: Mon, 29 Jul 2024 11:14:30 +0200 Subject: [PATCH 064/411] chore: add fake data to the dataProviders --- dataProviders.json | 6 ++++++ types/components.ts | 2 ++ 2 files changed, 8 insertions(+) diff --git a/dataProviders.json b/dataProviders.json index 5199fe7228..ab8a058620 100644 --- a/dataProviders.json +++ b/dataProviders.json @@ -13,6 +13,8 @@ "provider": "aws", "provider_id": "1234567890", "alias": "mock_aws_connected", + "status": "cancelled", + "resources": 101, "connection": { "connected": true, "last_checked_at": "2024-07-17T09:55:14.191475Z" @@ -40,6 +42,8 @@ "provider": "azure", "provider_id": "1234567891", "alias": "mock_aws_not_connected", + "status": "pending", + "resources": 222, "connection": { "connected": false, "last_checked_at": "2024-07-17T09:55:18.987425Z" @@ -67,6 +71,8 @@ "provider": "gcp", "provider_id": "1234567895", "alias": "mock_gcp", + "status": "completed", + "resources": 143, "connection": { "connected": true, "last_checked_at": "2024-07-17T09:55:18.987425Z" diff --git a/types/components.ts b/types/components.ts index 14bfe0b219..994adf02a2 100644 --- a/types/components.ts +++ b/types/components.ts @@ -16,6 +16,8 @@ export interface ProviderProps { provider: "aws" | "azure" | "gcp"; provider_id: string; alias: string; + status: "completed" | "pending" | "cancelled"; + resources: number; connection: { connected: boolean; last_checked_at: string; From 151fca146ec764e8936990776e6939e9de5b3451 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Mon, 29 Jul 2024 11:16:18 +0200 Subject: [PATCH 065/411] chore: remove the old table and rename ProviderInfo component --- app/(prowler)/providers/data.ts | 371 ---------------- .../{AccountInfo.tsx => ProviderInfo.tsx} | 16 +- components/ui/table/CustomTable.tsx | 406 ------------------ 3 files changed, 8 insertions(+), 785 deletions(-) delete mode 100644 app/(prowler)/providers/data.ts rename components/providers/{AccountInfo.tsx => ProviderInfo.tsx} (76%) delete mode 100644 components/ui/table/CustomTable.tsx diff --git a/app/(prowler)/providers/data.ts b/app/(prowler)/providers/data.ts deleted file mode 100644 index 98f013fdcd..0000000000 --- a/app/(prowler)/providers/data.ts +++ /dev/null @@ -1,371 +0,0 @@ -export type Status = "active" | "paused" | "warning"; - -export const statusColorMap: Record = - { - active: "success", - paused: "danger", - warning: "warning", - }; - -export interface Provider { - id: number; - account: string; - provider_id: number; - connected: boolean; - group: string; - scan_status: Status; - scan_status_time: string; - last_scan: string; - next_scan: string; - provider_img: string; - resources: number; - added: string; - actions?: React.ReactNode; -} - -export const statusOptions = [ - { name: "Active", uid: "active" }, - { name: "Paused", uid: "paused" }, - { name: "Warning", uid: "warning" }, -]; - -// Define columns explicitly -export const columns = [ - { name: "ID", uid: "id", sortable: true, initial_visible: false }, - { name: "ACCOUNT", uid: "account", sortable: false, initial_visible: true }, - { name: "GROUP(s)", uid: "group", sortable: false, initial_visible: false }, - { - name: "SCAN STATUS", - uid: "scan_status", - sortable: false, - initial_visible: false, - }, - { - name: "LAST SCAN", - uid: "last_scan", - sortable: false, - initial_visible: true, - }, - { - name: "NEXT SCAN", - uid: "next_scan", - sortable: false, - initial_visible: true, - }, - { - name: "RESOURCES", - uid: "resources", - sortable: true, - initial_visible: true, - }, - { - name: "ADDED", - uid: "added", - sortable: true, - initial_visible: true, - }, - { name: "ACTIONS", uid: "actions", initial_visible: false }, -]; - -export const providers = [ - { - id: 1, - account: "aws", - provider_id: 740350143844, - connected: true, - group: "Production, Bank 1, Development", - scan_status: "active", - scan_status_time: "5 minutes, 4 seconds", - last_scan: "Jul 17, 2024 4:46pm UTC", - next_scan: "Jul 18, 2024 4:46pm UTC", - provider_img: - "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgdmlld0JveD0iMCAwIDI1NiAyNTYiPjxnIGZpbGw9Im5vbmUiPjxyZWN0IHdpZHRoPSIyNTYiIGhlaWdodD0iMjU2IiBmaWxsPSIjZjRmMmVkIiByeD0iNjAiLz48cGF0aCBmaWxsPSIjMjUyZjNlIiBkPSJNODQuNzQ1IDExMS45NjFjMCAyLjQzNC4yNjMgNC40MDcuNzIzIDUuODU1YTM1IDM1IDAgMCAwIDIuMTA2IDQuNzM3Yy4zMjkuNTI2LjQ2IDEuMDUyLjQ2IDEuNTEzYzAgLjY1OC0uMzk1IDEuMzE2LTEuMjUgMS45NzNsLTQuMTQ1IDIuNzY0Yy0uNTkyLjM5NC0xLjE4NC41OTItMS43MS41OTJjLS42NTggMC0xLjMxNi0uMzI5LTEuOTc0LS45MjFhMjAuNCAyMC40IDAgMCAxLTIuMzY4LTMuMDkyYTUxIDUxIDAgMCAxLTIuMDQtMy44ODJxLTcuNjk3IDkuMDgtMTkuMzQyIDkuMDc5Yy01LjUyNiAwLTkuOTM0LTEuNTc5LTEzLjE1OC00LjczN2MtMy4yMjMtMy4xNTgtNC44NjgtNy4zNjgtNC44NjgtMTIuNjMxYzAtNS41OTMgMS45NzQtMTAuMTMyIDUuOTg3LTEzLjU1M3M5LjM0Mi01LjEzMiAxNi4xMTgtNS4xMzJjMi4yMzcgMCA0LjU0LjE5OCA2Ljk3NC41MjdzNC45MzQuODU1IDcuNTY2IDEuNDQ3di00LjgwM2MwLTUtMS4wNTMtOC40ODctMy4wOTItMTAuNTI2Yy0yLjEwNi0yLjA0LTUuNjU4LTMuMDI2LTEwLjcyNC0zLjAyNmMtMi4zMDMgMC00LjY3MS4yNjMtNy4xMDUuODU1cy00LjgwMyAxLjMxNi03LjEwNiAyLjIzN2ExOSAxOSAwIDAgMS0yLjMwMi44NTVjLS40Ni4xMzItLjc5LjE5OC0xLjA1My4xOThjLS45MiAwLTEuMzgyLS42NTgtMS4zODItMi4wNHYtMy4yMjRjMC0xLjA1Mi4xMzItMS44NDIuNDYxLTIuMzAycy45MjEtLjkyMSAxLjg0Mi0xLjM4MnEzLjQ1NC0xLjc3NiA4LjI5LTIuOTZjMy4yMjMtLjg1NiA2LjY0NC0xLjI1IDEwLjI2My0xLjI1YzcuODI5IDAgMTMuNTUyIDEuNzc2IDE3LjIzNyA1LjMyOGMzLjYxOCAzLjU1MyA1LjQ2IDguOTQ4IDUuNDYgMTYuMTg1djIxLjMxNnptLTI2LjcxIDEwYzIuMTcgMCA0LjQwNy0uMzk1IDYuNzc2LTEuMTg1YzIuMzY4LS43ODkgNC40NzMtMi4yMzcgNi4yNS00LjIxYzEuMDUyLTEuMjUgMS44NDItMi42MzIgMi4yMzYtNC4yMTFzLjY1OC0zLjQ4Ny42NTgtNS43MjN2LTIuNzY0YTU1IDU1IDAgMCAwLTYuMDUyLTEuMTE4YTUwIDUwIDAgMCAwLTYuMTg1LS4zOTVjLTQuNDA4IDAtNy42MzEuODU2LTkuODAyIDIuNjMycy0zLjIyNCA0LjI3Ni0zLjIyNCA3LjU2NmMwIDMuMDkyLjc5IDUuMzk0IDIuNDM0IDYuOTczYzEuNTggMS42NDUgMy44ODIgMi40MzUgNi45MDggMi40MzVtNTIuODI4IDcuMTA1Yy0xLjE4NCAwLTEuOTc0LS4xOTgtMi41LS42NThjLS41MjYtLjM5NS0uOTg3LTEuMzE2LTEuMzgxLTIuNTY2bC0xNS40Ni01MC44NTVjLS4zOTYtMS4zMTYtLjU5My0yLjE3MS0uNTkzLTIuNjMyYzAtMS4wNTIuNTI2LTEuNjQ1IDEuNTc5LTEuNjQ1aDYuNDQ3YzEuMjUgMCAyLjEwNi4xOTggMi41NjYuNjU4Yy41MjYuMzk1LjkyMSAxLjMxNiAxLjMxNiAyLjU2NmwxMS4wNTIgNDMuNTUzbDEwLjI2NC00My41NTNjLjMyOS0xLjMxNi43MjMtMi4xNyAxLjI1LTIuNTY2Yy41MjYtLjM5NCAxLjQ0Ny0uNjU3IDIuNjMxLS42NTdoNS4yNjNjMS4yNSAwIDIuMTA2LjE5NyAyLjYzMi42NTdjLjUyNi4zOTUuOTg3IDEuMzE2IDEuMjUgMi41NjZsMTAuMzk1IDQ0LjA3OWwxMS4zODEtNDQuMDc5Yy4zOTUtMS4zMTYuODU2LTIuMTcgMS4zMTYtMi41NjZjLjUyNi0uMzk0IDEuMzgyLS42NTcgMi41NjYtLjY1N2g2LjExOGMxLjA1MyAwIDEuNjQ1LjUyNiAxLjY0NSAxLjY0NGMwIC4zMy0uMDY2LjY1OC0uMTMyIDEuMDUzYy0uMDY1LjM5NS0uMTk3LjkyLS40NiAxLjY0NWwtMTUuODU1IDUwLjg1NXEtLjU5MyAxLjk3NC0xLjM4MiAyLjU2NmMtLjUyNi4zOTQtMS4zODIuNjU4LTIuNS42NThoLTUuNjU4Yy0xLjI1IDAtMi4xMDUtLjE5OC0yLjYzMS0uNjU4Yy0uNTI3LS40NjEtLjk4Ny0xLjMxNi0xLjI1LTIuNjMybC0xMC4xOTgtNDIuNDM0bC0xMC4xMzEgNDIuMzY4Yy0uMzI5IDEuMzE2LS43MjQgMi4xNzEtMS4yNSAyLjYzMmMtLjUyNy40Ni0xLjQ0OC42NTgtMi42MzIuNjU4em04NC41NCAxLjc3NmMtMy40MjEgMC02Ljg0Mi0uMzk1LTEwLjEzMi0xLjE4NGMtMy4yODktLjc5LTUuODU1LTEuNjQ1LTcuNTY2LTIuNjMyYy0xLjA1Mi0uNTkyLTEuNzc2LTEuMjUtMi4wMzktMS44NDJhNC42NSA0LjY1IDAgMCAxLS4zOTUtMS44NDJ2LTMuMzU1YzAtMS4zODIuNTI2LTIuMDQgMS41MTMtMi4wNHEuNTkzIDAgMS4xODQuMTk4Yy4zOTUuMTMxLjk4Ny4zOTQgMS42NDUuNjU4YTM1LjggMzUuOCAwIDAgMCA3LjIzNyAyLjMwMmEzOS41IDM5LjUgMCAwIDAgNy44MjkuNzljNC4xNDUgMCA3LjM2OC0uNzI0IDkuNjA1LTIuMTcxYzIuMjM3LTEuNDQ4IDMuNDIxLTMuNTUzIDMuNDIxLTYuMjVjMC0xLjg0Mi0uNTkyLTMuMzU2LTEuNzc2LTQuNjA2cy0zLjQyMS0yLjM2OC02LjY0NS0zLjQyMWwtOS41MzktMi45NmMtNC44MDMtMS41MTMtOC4zNTYtMy43NS0xMC41MjctNi43MWMtMi4xNzEtMi44OTUtMy4yODktNi4xMi0zLjI4OS05LjU0cTAtNC4xNDQgMS43NzYtNy4zMDNjMS4xODQtMi4xMDUgMi43NjMtMy45NDcgNC43MzctNS4zOTRjMS45NzQtMS41MTQgNC4yMTEtMi42MzIgNi44NDItMy40MjJjMi42MzItLjc5IDUuMzk1LTEuMTE4IDguMjktMS4xMThjMS40NDcgMCAyLjk2LjA2NiA0LjQwOC4yNjNjMS41MTMuMTk3IDIuODk0LjQ2IDQuMjc2LjcyNGMxLjMxNi4zMjkgMi41NjYuNjU4IDMuNzUgMS4wNTNxMS43NzYuNTkxIDIuNzYzIDEuMTg0Yy45MjEuNTI2IDEuNTc5IDEuMDUyIDEuOTc0IDEuNjQ0cS41OTIuNzkuNTkyIDIuMTcydjMuMDkyYzAgMS4zODEtLjUyNiAyLjEwNS0xLjUxMyAyLjEwNWMtLjUyNyAwLTEuMzgyLS4yNjMtMi41LS43OXEtNS42MjUtMi41NjUtMTIuNjMyLTIuNTY1Yy0zLjc1IDAtNi43MS41OTItOC43NSAxLjg0MnMtMy4wOTIgMy4xNTgtMy4wOTIgNS44NTVjMCAxLjg0Mi42NTggMy40MjEgMS45NzQgNC42NzFzMy43NSAyLjUgNy4yMzcgMy42MThsOS4zNDIgMi45NmM0LjczNiAxLjUxNCA4LjE1OCAzLjYxOSAxMC4xOTcgNi4zMTdzMy4wMjYgNS43ODkgMy4wMjYgOS4yMWMwIDIuODI5LS41OTIgNS4zOTUtMS43MSA3LjYzMmMtMS4xODQgMi4yMzctMi43NjMgNC4yMS00LjgwMyA1Ljc4OWMtMi4wMzkgMS42NDUtNC40NzQgMi44MjktNy4zMDIgMy42ODVjLTIuOTYxLjkyMS02LjA1MyAxLjM4MS05LjQwOCAxLjM4MSIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIwNy44MzcgMTYyLjgxNmMtMjEuNjQ1IDE1Ljk4Ny01My4wOTIgMjQuNDc0LTgwLjEzMiAyNC40NzRjLTM3Ljg5NCAwLTcyLjA0LTE0LjAxNC05Ny44MjktMzcuMzAzYy0yLjA0LTEuODQyLS4xOTctNC4zNDIgMi4yMzctMi44OTVjMjcuODk1IDE2LjE4NCA2Mi4zMDMgMjUuOTg3IDk3Ljg5NSAyNS45ODdjMjQuMDEzIDAgNTAuMzk1LTUgNzQuNjcxLTE1LjI2M2MzLjYxOC0xLjY0NSA2LjcxIDIuMzY4IDMuMTU4IDUiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIxNi44NSAxNTIuNTUzYy0yLjc2My0zLjU1My0xOC4yODktMS43MTEtMjUuMzI5LS44NTZjLTIuMTA1LjI2NC0yLjQzNC0xLjU3OS0uNTI2LTIuOTZjMTIuMzY4LTguNjg0IDMyLjY5Ny02LjE4NCAzNS4wNjYtMy4yOWMyLjM2OCAyLjk2MS0uNjU4IDIzLjI5LTEyLjIzNyAzMy4wMjdjLTEuNzc3IDEuNTEzLTMuNDg3LjcyMy0yLjY5OC0xLjI1YzIuNjMyLTYuNTEzIDguNDg3LTIxLjE4NSA1LjcyNC0yNC42NzEiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=", - resources: 132, - added: "Apr 1, 2024", - }, - { - id: 2, - account: "gcp", - provider_id: 740350143845, - connected: false, - group: "Development", - scan_status: "paused", - scan_status_time: "10 minutes, 15 seconds", - last_scan: "Jul 17, 2024 3:46pm UTC", - next_scan: "Jul 18, 2024 3:46pm UTC", - provider_img: - "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgdmlld0JveD0iMCAwIDI1NiAyNTYiPjxnIGZpbGw9Im5vbmUiPjxyZWN0IHdpZHRoPSIyNTYiIGhlaWdodD0iMjU2IiBmaWxsPSIjZjRmMmVkIiByeD0iNjAiLz48cGF0aCBmaWxsPSIjMjUyZjNlIiBkPSJNODQuNzQ1IDExMS45NjFjMCAyLjQzNC4yNjMgNC40MDcuNzIzIDUuODU1YTM1IDM1IDAgMCAwIDIuMTA2IDQuNzM3Yy4zMjkuNTI2LjQ2IDEuMDUyLjQ2IDEuNTEzYzAgLjY1OC0uMzk1IDEuMzE2LTEuMjUgMS45NzNsLTQuMTQ1IDIuNzY0Yy0uNTkyLjM5NC0xLjE4NC41OTItMS43MS41OTJjLS42NTggMC0xLjMxNi0uMzI5LTEuOTc0LS45MjFhMjAuNCAyMC40IDAgMCAxLTIuMzY4LTMuMDkyYTUxIDUxIDAgMCAxLTIuMDQtMy44ODJxLTcuNjk3IDkuMDgtMTkuMzQyIDkuMDc5Yy01LjUyNiAwLTkuOTM0LTEuNTc5LTEzLjE1OC00LjczN2MtMy4yMjMtMy4xNTgtNC44NjgtNy4zNjgtNC44NjgtMTIuNjMxYzAtNS41OTMgMS45NzQtMTAuMTMyIDUuOTg3LTEzLjU1M3M5LjM0Mi01LjEzMiAxNi4xMTgtNS4xMzJjMi4yMzcgMCA0LjU0LjE5OCA2Ljk3NC41MjdzNC45MzQuODU1IDcuNTY2IDEuNDQ3di00LjgwM2MwLTUtMS4wNTMtOC40ODctMy4wOTItMTAuNTI2Yy0yLjEwNi0yLjA0LTUuNjU4LTMuMDI2LTEwLjcyNC0zLjAyNmMtMi4zMDMgMC00LjY3MS4yNjMtNy4xMDUuODU1cy00LjgwMyAxLjMxNi03LjEwNiAyLjIzN2ExOSAxOSAwIDAgMS0yLjMwMi44NTVjLS40Ni4xMzItLjc5LjE5OC0xLjA1My4xOThjLS45MiAwLTEuMzgyLS42NTgtMS4zODItMi4wNHYtMy4yMjRjMC0xLjA1Mi4xMzItMS44NDIuNDYxLTIuMzAycy45MjEtLjkyMSAxLjg0Mi0xLjM4MnEzLjQ1NC0xLjc3NiA4LjI5LTIuOTZjMy4yMjMtLjg1NiA2LjY0NC0xLjI1IDEwLjI2My0xLjI1YzcuODI5IDAgMTMuNTUyIDEuNzc2IDE3LjIzNyA1LjMyOGMzLjYxOCAzLjU1MyA1LjQ2IDguOTQ4IDUuNDYgMTYuMTg1djIxLjMxNnptLTI2LjcxIDEwYzIuMTcgMCA0LjQwNy0uMzk1IDYuNzc2LTEuMTg1YzIuMzY4LS43ODkgNC40NzMtMi4yMzcgNi4yNS00LjIxYzEuMDUyLTEuMjUgMS44NDItMi42MzIgMi4yMzYtNC4yMTFzLjY1OC0zLjQ4Ny42NTgtNS43MjN2LTIuNzY0YTU1IDU1IDAgMCAwLTYuMDUyLTEuMTE4YTUwIDUwIDAgMCAwLTYuMTg1LS4zOTVjLTQuNDA4IDAtNy42MzEuODU2LTkuODAyIDIuNjMycy0zLjIyNCA0LjI3Ni0zLjIyNCA3LjU2NmMwIDMuMDkyLjc5IDUuMzk0IDIuNDM0IDYuOTczYzEuNTggMS42NDUgMy44ODIgMi40MzUgNi45MDggMi40MzVtNTIuODI4IDcuMTA1Yy0xLjE4NCAwLTEuOTc0LS4xOTgtMi41LS42NThjLS41MjYtLjM5NS0uOTg3LTEuMzE2LTEuMzgxLTIuNTY2bC0xNS40Ni01MC44NTVjLS4zOTYtMS4zMTYtLjU5My0yLjE3MS0uNTkzLTIuNjMyYzAtMS4wNTIuNTI2LTEuNjQ1IDEuNTc5LTEuNjQ1aDYuNDQ3YzEuMjUgMCAyLjEwNi4xOTggMi41NjYuNjU4Yy41MjYuMzk1LjkyMSAxLjMxNiAxLjMxNiAyLjU2NmwxMS4wNTIgNDMuNTUzbDEwLjI2NC00My41NTNjLjMyOS0xLjMxNi43MjMtMi4xNyAxLjI1LTIuNTY2Yy41MjYtLjM5NCAxLjQ0Ny0uNjU3IDIuNjMxLS42NTdoNS4yNjNjMS4yNSAwIDIuMTA2LjE5NyAyLjYzMi42NTdjLjUyNi4zOTUuOTg3IDEuMzE2IDEuMjUgMi41NjZsMTAuMzk1IDQ0LjA3OWwxMS4zODEtNDQuMDc5Yy4zOTUtMS4zMTYuODU2LTIuMTcgMS4zMTYtMi41NjZjLjUyNi0uMzk0IDEuMzgyLS42NTcgMi41NjYtLjY1N2g2LjExOGMxLjA1MyAwIDEuNjQ1LjUyNiAxLjY0NSAxLjY0NGMwIC4zMy0uMDY2LjY1OC0uMTMyIDEuMDUzYy0uMDY1LjM5NS0uMTk3LjkyLS40NiAxLjY0NWwtMTUuODU1IDUwLjg1NXEtLjU5MyAxLjk3NC0xLjM4MiAyLjU2NmMtLjUyNi4zOTQtMS4zODIuNjU4LTIuNS42NThoLTUuNjU4Yy0xLjI1IDAtMi4xMDUtLjE5OC0yLjYzMS0uNjU4Yy0uNTI3LS40NjEtLjk4Ny0xLjMxNi0xLjI1LTIuNjMybC0xMC4xOTgtNDIuNDM0bC0xMC4xMzEgNDIuMzY4Yy0uMzI5IDEuMzE2LS43MjQgMi4xNzEtMS4yNSAyLjYzMmMtLjUyNy40Ni0xLjQ0OC42NTgtMi42MzIuNjU4em04NC41NCAxLjc3NmMtMy40MjEgMC02Ljg0Mi0uMzk1LTEwLjEzMi0xLjE4NGMtMy4yODktLjc5LTUuODU1LTEuNjQ1LTcuNTY2LTIuNjMyYy0xLjA1Mi0uNTkyLTEuNzc2LTEuMjUtMi4wMzktMS44NDJhNC42NSA0LjY1IDAgMCAxLS4zOTUtMS44NDJ2LTMuMzU1YzAtMS4zODIuNTI2LTIuMDQgMS41MTMtMi4wNHEuNTkzIDAgMS4xODQuMTk4Yy4zOTUuMTMxLjk4Ny4zOTQgMS42NDUuNjU4YTM1LjggMzUuOCAwIDAgMCA3LjIzNyAyLjMwMmEzOS41IDM5LjUgMCAwIDAgNy44MjkuNzljNC4xNDUgMCA3LjM2OC0uNzI0IDkuNjA1LTIuMTcxYzIuMjM3LTEuNDQ4IDMuNDIxLTMuNTUzIDMuNDIxLTYuMjVjMC0xLjg0Mi0uNTkyLTMuMzU2LTEuNzc2LTQuNjA2cy0zLjQyMS0yLjM2OC02LjY0NS0zLjQyMWwtOS41MzktMi45NmMtNC44MDMtMS41MTMtOC4zNTYtMy43NS0xMC41MjctNi43MWMtMi4xNzEtMi44OTUtMy4yODktNi4xMi0zLjI4OS05LjU0cTAtNC4xNDQgMS43NzYtNy4zMDNjMS4xODQtMi4xMDUgMi43NjMtMy45NDcgNC43MzctNS4zOTRjMS45NzQtMS41MTQgNC4yMTEtMi42MzIgNi44NDItMy40MjJjMi42MzItLjc5IDUuMzk1LTEuMTE4IDguMjktMS4xMThjMS40NDcgMCAyLjk2LjA2NiA0LjQwOC4yNjNjMS41MTMuMTk3IDIuODk0LjQ2IDQuMjc2LjcyNGMxLjMxNi4zMjkgMi41NjYuNjU4IDMuNzUgMS4wNTNxMS43NzYuNTkxIDIuNzYzIDEuMTg0Yy45MjEuNTI2IDEuNTc5IDEuMDUyIDEuOTc0IDEuNjQ0cS41OTIuNzkuNTkyIDIuMTcydjMuMDkyYzAgMS4zODEtLjUyNiAyLjEwNS0xLjUxMyAyLjEwNWMtLjUyNyAwLTEuMzgyLS4yNjMtMi41LS43OXEtNS42MjUtMi41NjUtMTIuNjMyLTIuNTY1Yy0zLjc1IDAtNi43MS41OTItOC43NSAxLjg0MnMtMy4wOTIgMy4xNTgtMy4wOTIgNS44NTVjMCAxLjg0Mi42NTggMy40MjEgMS45NzQgNC42NzFzMy43NSAyLjUgNy4yMzcgMy42MThsOS4zNDIgMi45NmM0LjczNiAxLjUxNCA4LjE1OCAzLjYxOSAxMC4xOTcgNi4zMTdzMy4wMjYgNS43ODkgMy4wMjYgOS4yMWMwIDIuODI5LS41OTIgNS4zOTUtMS43MSA3LjYzMmMtMS4xODQgMi4yMzctMi43NjMgNC4yMS00LjgwMyA1Ljc4OWMtMi4wMzkgMS42NDUtNC40NzQgMi44MjktNy4zMDIgMy42ODVjLTIuOTYxLjkyMS02LjA1MyAxLjM4MS05LjQwOCAxLjM4MSIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIwNy44MzcgMTYyLjgxNmMtMjEuNjQ1IDE1Ljk4Ny01My4wOTIgMjQuNDc0LTgwLjEzMiAyNC40NzRjLTM3Ljg5NCAwLTcyLjA0LTE0LjAxNC05Ny44MjktMzcuMzAzYy0yLjA0LTEuODQyLS4xOTctNC4zNDIgMi4yMzctMi44OTVjMjcuODk1IDE2LjE4NCA2Mi4zMDMgMjUuOTg3IDk3Ljg5NSAyNS45ODdjMjQuMDEzIDAgNTAuMzk1LTUgNzQuNjcxLTE1LjI2M2MzLjYxOC0xLjY0NSA2LjcxIDIuMzY4IDMuMTU4IDUiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIxNi44NSAxNTIuNTUzYy0yLjc2My0zLjU1My0xOC4yODktMS43MTEtMjUuMzI5LS44NTZjLTIuMTA1LjI2NC0yLjQzNC0xLjU3OS0uNTI2LTIuOTZjMTIuMzY4LTguNjg0IDMyLjY5Ny02LjE4NCAzNS4wNjYtMy4yOWMyLjM2OCAyLjk2MS0uNjU4IDIzLjI5LTEyLjIzNyAzMy4wMjdjLTEuNzc3IDEuNTEzLTMuNDg3LjcyMy0yLjY5OC0xLjI1YzIuNjMyLTYuNTEzIDguNDg3LTIxLjE4NSA1LjcyNC0yNC42NzEiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=", - resources: 98, - added: "Mar 15, 2024", - }, - { - id: 3, - account: "azure", - provider_id: 740350143846, - connected: true, - group: "Production, Development", - scan_status: "active", - scan_status_time: "2 minutes, 30 seconds", - last_scan: "Jul 17, 2024 2:46pm UTC", - next_scan: "Jul 18, 2024 2:46pm UTC", - provider_img: - "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgdmlld0JveD0iMCAwIDI1NiAyNTYiPjxnIGZpbGw9Im5vbmUiPjxyZWN0IHdpZHRoPSIyNTYiIGhlaWdodD0iMjU2IiBmaWxsPSIjZjRmMmVkIiByeD0iNjAiLz48cGF0aCBmaWxsPSIjMjUyZjNlIiBkPSJNODQuNzQ1IDExMS45NjFjMCAyLjQzNC4yNjMgNC40MDcuNzIzIDUuODU1YTM1IDM1IDAgMCAwIDIuMTA2IDQuNzM3Yy4zMjkuNTI2LjQ2IDEuMDUyLjQ2IDEuNTEzYzAgLjY1OC0uMzk1IDEuMzE2LTEuMjUgMS45NzNsLTQuMTQ1IDIuNzY0Yy0uNTkyLjM5NC0xLjE4NC41OTItMS43MS41OTJjLS42NTggMC0xLjMxNi0uMzI5LTEuOTc0LS45MjFhMjAuNCAyMC40IDAgMCAxLTIuMzY4LTMuMDkyYTUxIDUxIDAgMCAxLTIuMDQtMy44ODJxLTcuNjk3IDkuMDgtMTkuMzQyIDkuMDc5Yy01LjUyNiAwLTkuOTM0LTEuNTc5LTEzLjE1OC00LjczN2MtMy4yMjMtMy4xNTgtNC44NjgtNy4zNjgtNC44NjgtMTIuNjMxYzAtNS41OTMgMS45NzQtMTAuMTMyIDUuOTg3LTEzLjU1M3M5LjM0Mi01LjEzMiAxNi4xMTgtNS4xMzJjMi4yMzcgMCA0LjU0LjE5OCA2Ljk3NC41MjdzNC45MzQuODU1IDcuNTY2IDEuNDQ3di00LjgwM2MwLTUtMS4wNTMtOC40ODctMy4wOTItMTAuNTI2Yy0yLjEwNi0yLjA0LTUuNjU4LTMuMDI2LTEwLjcyNC0zLjAyNmMtMi4zMDMgMC00LjY3MS4yNjMtNy4xMDUuODU1cy00LjgwMyAxLjMxNi03LjEwNiAyLjIzN2ExOSAxOSAwIDAgMS0yLjMwMi44NTVjLS40Ni4xMzItLjc5LjE5OC0xLjA1My4xOThjLS45MiAwLTEuMzgyLS42NTgtMS4zODItMi4wNHYtMy4yMjRjMC0xLjA1Mi4xMzItMS44NDIuNDYxLTIuMzAycy45MjEtLjkyMSAxLjg0Mi0xLjM4MnEzLjQ1NC0xLjc3NiA4LjI5LTIuOTZjMy4yMjMtLjg1NiA2LjY0NC0xLjI1IDEwLjI2My0xLjI1YzcuODI5IDAgMTMuNTUyIDEuNzc2IDE3LjIzNyA1LjMyOGMzLjYxOCAzLjU1MyA1LjQ2IDguOTQ4IDUuNDYgMTYuMTg1djIxLjMxNnptLTI2LjcxIDEwYzIuMTcgMCA0LjQwNy0uMzk1IDYuNzc2LTEuMTg1YzIuMzY4LS43ODkgNC40NzMtMi4yMzcgNi4yNS00LjIxYzEuMDUyLTEuMjUgMS44NDItMi42MzIgMi4yMzYtNC4yMTFzLjY1OC0zLjQ4Ny42NTgtNS43MjN2LTIuNzY0YTU1IDU1IDAgMCAwLTYuMDUyLTEuMTE4YTUwIDUwIDAgMCAwLTYuMTg1LS4zOTVjLTQuNDA4IDAtNy42MzEuODU2LTkuODAyIDIuNjMycy0zLjIyNCA0LjI3Ni0zLjIyNCA3LjU2NmMwIDMuMDkyLjc5IDUuMzk0IDIuNDM0IDYuOTczYzEuNTggMS42NDUgMy44ODIgMi40MzUgNi45MDggMi40MzVtNTIuODI4IDcuMTA1Yy0xLjE4NCAwLTEuOTc0LS4xOTgtMi41LS42NThjLS41MjYtLjM5NS0uOTg3LTEuMzE2LTEuMzgxLTIuNTY2bC0xNS40Ni01MC44NTVjLS4zOTYtMS4zMTYtLjU5My0yLjE3MS0uNTkzLTIuNjMyYzAtMS4wNTIuNTI2LTEuNjQ1IDEuNTc5LTEuNjQ1aDYuNDQ3YzEuMjUgMCAyLjEwNi4xOTggMi41NjYuNjU4Yy41MjYuMzk1LjkyMSAxLjMxNiAxLjMxNiAyLjU2NmwxMS4wNTIgNDMuNTUzbDEwLjI2NC00My41NTNjLjMyOS0xLjMxNi43MjMtMi4xNyAxLjI1LTIuNTY2Yy41MjYtLjM5NCAxLjQ0Ny0uNjU3IDIuNjMxLS42NTdoNS4yNjNjMS4yNSAwIDIuMTA2LjE5NyAyLjYzMi42NTdjLjUyNi4zOTUuOTg3IDEuMzE2IDEuMjUgMi41NjZsMTAuMzk1IDQ0LjA3OWwxMS4zODEtNDQuMDc5Yy4zOTUtMS4zMTYuODU2LTIuMTcgMS4zMTYtMi41NjZjLjUyNi0uMzk0IDEuMzgyLS42NTcgMi41NjYtLjY1N2g2LjExOGMxLjA1MyAwIDEuNjQ1LjUyNiAxLjY0NSAxLjY0NGMwIC4zMy0uMDY2LjY1OC0uMTMyIDEuMDUzYy0uMDY1LjM5NS0uMTk3LjkyLS40NiAxLjY0NWwtMTUuODU1IDUwLjg1NXEtLjU5MyAxLjk3NC0xLjM4MiAyLjU2NmMtLjUyNi4zOTQtMS4zODIuNjU4LTIuNS42NThoLTUuNjU4Yy0xLjI1IDAtMi4xMDUtLjE5OC0yLjYzMS0uNjU4Yy0uNTI3LS40NjEtLjk4Ny0xLjMxNi0xLjI1LTIuNjMybC0xMC4xOTgtNDIuNDM0bC0xMC4xMzEgNDIuMzY4Yy0uMzI5IDEuMzE2LS43MjQgMi4xNzEtMS4yNSAyLjYzMmMtLjUyNy40Ni0xLjQ0OC42NTgtMi42MzIuNjU4em04NC41NCAxLjc3NmMtMy40MjEgMC02Ljg0Mi0uMzk1LTEwLjEzMi0xLjE4NGMtMy4yODktLjc5LTUuODU1LTEuNjQ1LTcuNTY2LTIuNjMyYy0xLjA1Mi0uNTkyLTEuNzc2LTEuMjUtMi4wMzktMS44NDJhNC42NSA0LjY1IDAgMCAxLS4zOTUtMS44NDJ2LTMuMzU1YzAtMS4zODIuNTI2LTIuMDQgMS41MTMtMi4wNHEuNTkzIDAgMS4xODQuMTk4Yy4zOTUuMTMxLjk4Ny4zOTQgMS42NDUuNjU4YTM1LjggMzUuOCAwIDAgMCA3LjIzNyAyLjMwMmEzOS41IDM5LjUgMCAwIDAgNy44MjkuNzljNC4xNDUgMCA3LjM2OC0uNzI0IDkuNjA1LTIuMTcxYzIuMjM3LTEuNDQ4IDMuNDIxLTMuNTUzIDMuNDIxLTYuMjVjMC0xLjg0Mi0uNTkyLTMuMzU2LTEuNzc2LTQuNjA2cy0zLjQyMS0yLjM2OC02LjY0NS0zLjQyMWwtOS41MzktMi45NmMtNC44MDMtMS41MTMtOC4zNTYtMy43NS0xMC41MjctNi43MWMtMi4xNzEtMi44OTUtMy4yODktNi4xMi0zLjI4OS05LjU0cTAtNC4xNDQgMS43NzYtNy4zMDNjMS4xODQtMi4xMDUgMi43NjMtMy45NDcgNC43MzctNS4zOTRjMS45NzQtMS41MTQgNC4yMTEtMi42MzIgNi44NDItMy40MjJjMi42MzItLjc5IDUuMzk1LTEuMTE4IDguMjktMS4xMThjMS40NDcgMCAyLjk2LjA2NiA0LjQwOC4yNjNjMS41MTMuMTk3IDIuODk0LjQ2IDQuMjc2LjcyNGMxLjMxNi4zMjkgMi41NjYuNjU4IDMuNzUgMS4wNTNxMS43NzYuNTkxIDIuNzYzIDEuMTg0Yy45MjEuNTI2IDEuNTc5IDEuMDUyIDEuOTc0IDEuNjQ0cS41OTIuNzkuNTkyIDIuMTcydjMuMDkyYzAgMS4zODEtLjUyNiAyLjEwNS0xLjUxMyAyLjEwNWMtLjUyNyAwLTEuMzgyLS4yNjMtMi41LS43OXEtNS42MjUtMi41NjUtMTIuNjMyLTIuNTY1Yy0zLjc1IDAtNi43MS41OTItOC43NSAxLjg0MnMtMy4wOTIgMy4xNTgtMy4wOTIgNS44NTVjMCAxLjg0Mi42NTggMy40MjEgMS45NzQgNC42NzFzMy43NSAyLjUgNy4yMzcgMy42MThsOS4zNDIgMi45NmM0LjczNiAxLjUxNCA4LjE1OCAzLjYxOSAxMC4xOTcgNi4zMTdzMy4wMjYgNS43ODkgMy4wMjYgOS4yMWMwIDIuODI5LS41OTIgNS4zOTUtMS43MSA3LjYzMmMtMS4xODQgMi4yMzctMi43NjMgNC4yMS00LjgwMyA1Ljc4OWMtMi4wMzkgMS42NDUtNC40NzQgMi44MjktNy4zMDIgMy42ODVjLTIuOTYxLjkyMS02LjA1MyAxLjM4MS05LjQwOCAxLjM4MSIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIwNy44MzcgMTYyLjgxNmMtMjEuNjQ1IDE1Ljk4Ny01My4wOTIgMjQuNDc0LTgwLjEzMiAyNC40NzRjLTM3Ljg5NCAwLTcyLjA0LTE0LjAxNC05Ny44MjktMzcuMzAzYy0yLjA0LTEuODQyLS4xOTctNC4zNDIgMi4yMzctMi44OTVjMjcuODk1IDE2LjE4NCA2Mi4zMDMgMjUuOTg3IDk3Ljg5NSAyNS45ODdjMjQuMDEzIDAgNTAuMzk1LTUgNzQuNjcxLTE1LjI2M2MzLjYxOC0xLjY0NSA2LjcxIDIuMzY4IDMuMTU4IDUiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIxNi44NSAxNTIuNTUzYy0yLjc2My0zLjU1My0xOC4yODktMS43MTEtMjUuMzI5LS44NTZjLTIuMTA1LjI2NC0yLjQzNC0xLjU3OS0uNTI2LTIuOTZjMTIuMzY4LTguNjg0IDMyLjY5Ny02LjE4NCAzNS4wNjYtMy4yOWMyLjM2OCAyLjk2MS0uNjU4IDIzLjI5LTEyLjIzNyAzMy4wMjdjLTEuNzc3IDEuNTEzLTMuNDg3LjcyMy0yLjY5OC0xLjI1YzIuNjMyLTYuNTEzIDguNDg3LTIxLjE4NSA1LjcyNC0yNC42NzEiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=", - resources: 210, - added: "Feb 20, 2024", - }, - { - id: 4, - account: "digitalocean", - provider_id: 740350143847, - connected: true, - group: "Marketing, Development", - scan_status: "completed", - scan_status_time: "15 minutes, 10 seconds", - last_scan: "Jul 17, 2024 1:46pm UTC", - next_scan: "Jul 18, 2024 1:46pm UTC", - provider_img: - "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgdmlld0JveD0iMCAwIDI1NiAyNTYiPjxnIGZpbGw9Im5vbmUiPjxyZWN0IHdpZHRoPSIyNTYiIGhlaWdodD0iMjU2IiBmaWxsPSIjZjRmMmVkIiByeD0iNjAiLz48cGF0aCBmaWxsPSIjMjUyZjNlIiBkPSJNODQuNzQ1IDExMS45NjFjMCAyLjQzNC4yNjMgNC40MDcuNzIzIDUuODU1YTM1IDM1IDAgMCAwIDIuMTA2IDQuNzM3Yy4zMjkuNTI2LjQ2IDEuMDUyLjQ2IDEuNTEzYzAgLjY1OC0uMzk1IDEuMzE2LTEuMjUgMS45NzNsLTQuMTQ1IDIuNzY0Yy0uNTkyLjM5NC0xLjE4NC41OTItMS43MS41OTJjLS42NTggMC0xLjMxNi0uMzI5LTEuOTc0LS45MjFhMjAuNCAyMC40IDAgMCAxLTIuMzY4LTMuMDkyYTUxIDUxIDAgMCAxLTIuMDQtMy44ODJxLTcuNjk3IDkuMDgtMTkuMzQyIDkuMDc5Yy01LjUyNiAwLTkuOTM0LTEuNTc5LTEzLjE1OC00LjczN2MtMy4yMjMtMy4xNTgtNC44NjgtNy4zNjgtNC44NjgtMTIuNjMxYzAtNS41OTMgMS45NzQtMTAuMTMyIDUuOTg3LTEzLjU1M3M5LjM0Mi01LjEzMiAxNi4xMTgtNS4xMzJjMi4yMzcgMCA0LjU0LjE5OCA2Ljk3NC41MjdzNC45MzQuODU1IDcuNTY2IDEuNDQ3di00LjgwM2MwLTUtMS4wNTMtOC40ODctMy4wOTItMTAuNTI2Yy0yLjEwNi0yLjA0LTUuNjU4LTMuMDI2LTEwLjcyNC0zLjAyNmMtMi4zMDMgMC00LjY3MS4yNjMtNy4xMDUuODU1cy00LjgwMyAxLjMxNi03LjEwNiAyLjIzN2ExOSAxOSAwIDAgMS0yLjMwMi44NTVjLS40Ni4xMzItLjc5LjE5OC0xLjA1My4xOThjLS45MiAwLTEuMzgyLS42NTgtMS4zODItMi4wNHYtMy4yMjRjMC0xLjA1Mi4xMzItMS44NDIuNDYxLTIuMzAycy45MjEtLjkyMSAxLjg0Mi0xLjM4MnEzLjQ1NC0xLjc3NiA4LjI5LTIuOTZjMy4yMjMtLjg1NiA2LjY0NC0xLjI1IDEwLjI2My0xLjI1YzcuODI5IDAgMTMuNTUyIDEuNzc2IDE3LjIzNyA1LjMyOGMzLjYxOCAzLjU1MyA1LjQ2IDguOTQ4IDUuNDYgMTYuMTg1djIxLjMxNnptLTI2LjcxIDEwYzIuMTcgMCA0LjQwNy0uMzk1IDYuNzc2LTEuMTg1YzIuMzY4LS43ODkgNC40NzMtMi4yMzcgNi4yNS00LjIxYzEuMDUyLTEuMjUgMS44NDItMi42MzIgMi4yMzYtNC4yMTFzLjY1OC0zLjQ4Ny42NTgtNS43MjN2LTIuNzY0YTU1IDU1IDAgMCAwLTYuMDUyLTEuMTE4YTUwIDUwIDAgMCAwLTYuMTg1LS4zOTVjLTQuNDA4IDAtNy42MzEuODU2LTkuODAyIDIuNjMycy0zLjIyNCA0LjI3Ni0zLjIyNCA3LjU2NmMwIDMuMDkyLjc5IDUuMzk0IDIuNDM0IDYuOTczYzEuNTggMS42NDUgMy44ODIgMi40MzUgNi45MDggMi40MzVtNTIuODI4IDcuMTA1Yy0xLjE4NCAwLTEuOTc0LS4xOTgtMi41LS42NThjLS41MjYtLjM5NS0uOTg3LTEuMzE2LTEuMzgxLTIuNTY2bC0xNS40Ni01MC44NTVjLS4zOTYtMS4zMTYtLjU5My0yLjE3MS0uNTkzLTIuNjMyYzAtMS4wNTIuNTI2LTEuNjQ1IDEuNTc5LTEuNjQ1aDYuNDQ3YzEuMjUgMCAyLjEwNi4xOTggMi41NjYuNjU4Yy41MjYuMzk1LjkyMSAxLjMxNiAxLjMxNiAyLjU2NmwxMS4wNTIgNDMuNTUzbDEwLjI2NC00My41NTNjLjMyOS0xLjMxNi43MjMtMi4xNyAxLjI1LTIuNTY2Yy41MjYtLjM5NCAxLjQ0Ny0uNjU3IDIuNjMxLS42NTdoNS4yNjNjMS4yNSAwIDIuMTA2LjE5NyAyLjYzMi42NTdjLjUyNi4zOTUuOTg3IDEuMzE2IDEuMjUgMi41NjZsMTAuMzk1IDQ0LjA3OWwxMS4zODEtNDQuMDc5Yy4zOTUtMS4zMTYuODU2LTIuMTcgMS4zMTYtMi41NjZjLjUyNi0uMzk0IDEuMzgyLS42NTcgMi41NjYtLjY1N2g2LjExOGMxLjA1MyAwIDEuNjQ1LjUyNiAxLjY0NSAxLjY0NGMwIC4zMy0uMDY2LjY1OC0uMTMyIDEuMDUzYy0uMDY1LjM5NS0uMTk3LjkyLS40NiAxLjY0NWwtMTUuODU1IDUwLjg1NXEtLjU5MyAxLjk3NC0xLjM4MiAyLjU2NmMtLjUyNi4zOTQtMS4zODIuNjU4LTIuNS42NThoLTUuNjU4Yy0xLjI1IDAtMi4xMDUtLjE5OC0yLjYzMS0uNjU4Yy0uNTI3LS40NjEtLjk4Ny0xLjMxNi0xLjI1LTIuNjMybC0xMC4xOTgtNDIuNDM0bC0xMC4xMzEgNDIuMzY4Yy0uMzI5IDEuMzE2LS43MjQgMi4xNzEtMS4yNSAyLjYzMmMtLjUyNy40Ni0xLjQ0OC42NTgtMi42MzIuNjU4em04NC41NCAxLjc3NmMtMy40MjEgMC02Ljg0Mi0uMzk1LTEwLjEzMi0xLjE4NGMtMy4yODktLjc5LTUuODU1LTEuNjQ1LTcuNTY2LTIuNjMyYy0xLjA1Mi0uNTkyLTEuNzc2LTEuMjUtMi4wMzktMS44NDJhNC42NSA0LjY1IDAgMCAxLS4zOTUtMS44NDJ2LTMuMzU1YzAtMS4zODIuNTI2LTIuMDQgMS41MTMtMi4wNHEuNTkzIDAgMS4xODQuMTk4Yy4zOTUuMTMxLjk4Ny4zOTQgMS42NDUuNjU4YTM1LjggMzUuOCAwIDAgMCA3LjIzNyAyLjMwMmEzOS41IDM5LjUgMCAwIDAgNy44MjkuNzljNC4xNDUgMCA3LjM2OC0uNzI0IDkuNjA1LTIuMTcxYzIuMjM3LTEuNDQ4IDMuNDIxLTMuNTUzIDMuNDIxLTYuMjVjMC0xLjg0Mi0uNTkyLTMuMzU2LTEuNzc2LTQuNjA2cy0zLjQyMS0yLjM2OC02LjY0NS0zLjQyMWwtOS41MzktMi45NmMtNC44MDMtMS41MTMtOC4zNTYtMy43NS0xMC41MjctNi43MWMtMi4xNzEtMi44OTUtMy4yODktNi4xMi0zLjI4OS05LjU0cTAtNC4xNDQgMS43NzYtNy4zMDNjMS4xODQtMi4xMDUgMi43NjMtMy45NDcgNC43MzctNS4zOTRjMS45NzQtMS41MTQgNC4yMTEtMi42MzIgNi44NDItMy40MjJjMi42MzItLjc5IDUuMzk1LTEuMTE4IDguMjktMS4xMThjMS40NDcgMCAyLjk2LjA2NiA0LjQwOC4yNjNjMS41MTMuMTk3IDIuODk0LjQ2IDQuMjc2LjcyNGMxLjMxNi4zMjkgMi41NjYuNjU4IDMuNzUgMS4wNTNxMS43NzYuNTkxIDIuNzYzIDEuMTg0Yy45MjEuNTI2IDEuNTc5IDEuMDUyIDEuOTc0IDEuNjQ0cS41OTIuNzkuNTkyIDIuMTcydjMuMDkyYzAgMS4zODEtLjUyNiAyLjEwNS0xLjUxMyAyLjEwNWMtLjUyNyAwLTEuMzgyLS4yNjMtMi41LS43OXEtNS42MjUtMi41NjUtMTIuNjMyLTIuNTY1Yy0zLjc1IDAtNi43MS41OTItOC43NSAxLjg0MnMtMy4wOTIgMy4xNTgtMy4wOTIgNS44NTVjMCAxLjg0Mi42NTggMy40MjEgMS45NzQgNC42NzFzMy43NSAyLjUgNy4yMzcgMy42MThsOS4zNDIgMi45NmM0LjczNiAxLjUxNCA4LjE1OCAzLjYxOSAxMC4xOTcgNi4zMTdzMy4wMjYgNS43ODkgMy4wMjYgOS4yMWMwIDIuODI5LS41OTIgNS4zOTUtMS43MSA3LjYzMmMtMS4xODQgMi4yMzctMi43NjMgNC4yMS00LjgwMyA1Ljc4OWMtMi4wMzkgMS42NDUtNC40NzQgMi44MjktNy4zMDIgMy42ODVjLTIuOTYxLjkyMS02LjA1MyAxLjM4MS05LjQwOCAxLjM4MSIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIwNy44MzcgMTYyLjgxNmMtMjEuNjQ1IDE1Ljk4Ny01My4wOTIgMjQuNDc0LTgwLjEzMiAyNC40NzRjLTM3Ljg5NCAwLTcyLjA0LTE0LjAxNC05Ny44MjktMzcuMzAzYy0yLjA0LTEuODQyLS4xOTctNC4zNDIgMi4yMzctMi44OTVjMjcuODk1IDE2LjE4NCA2Mi4zMDMgMjUuOTg3IDk3Ljg5NSAyNS45ODdjMjQuMDEzIDAgNTAuMzk1LTUgNzQuNjcxLTE1LjI2M2MzLjYxOC0xLjY0NSA2LjcxIDIuMzY4IDMuMTU4IDUiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIxNi44NSAxNTIuNTUzYy0yLjc2My0zLjU1My0xOC4yODktMS43MTEtMjUuMzI5LS44NTZjLTIuMTA1LjI2NC0yLjQzNC0xLjU3OS0uNTI2LTIuOTZjMTIuMzY4LTguNjg0IDMyLjY5Ny02LjE4NCAzNS4wNjYtMy4yOWMyLjM2OCAyLjk2MS0uNjU4IDIzLjI5LTEyLjIzNyAzMy4wMjdjLTEuNzc3IDEuNTEzLTMuNDg3LjcyMy0yLjY5OC0xLjI1YzIuNjMyLTYuNTEzIDguNDg3LTIxLjE4NSA1LjcyNC0yNC42NzEiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=", - resources: 85, - added: "Jan 10, 2024", - }, - { - id: 5, - account: "aws", - provider_id: 740350143848, - connected: true, - group: "Sales", - scan_status: "active", - scan_status_time: "7 minutes, 45 seconds", - last_scan: "Jul 17, 2024 12:46pm UTC", - next_scan: "Jul 18, 2024 12:46pm UTC", - provider_img: - "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgdmlld0JveD0iMCAwIDI1NiAyNTYiPjxnIGZpbGw9Im5vbmUiPjxyZWN0IHdpZHRoPSIyNTYiIGhlaWdodD0iMjU2IiBmaWxsPSIjZjRmMmVkIiByeD0iNjAiLz48cGF0aCBmaWxsPSIjMjUyZjNlIiBkPSJNODQuNzQ1IDExMS45NjFjMCAyLjQzNC4yNjMgNC40MDcuNzIzIDUuODU1YTM1IDM1IDAgMCAwIDIuMTA2IDQuNzM3Yy4zMjkuNTI2LjQ2IDEuMDUyLjQ2IDEuNTEzYzAgLjY1OC0uMzk1IDEuMzE2LTEuMjUgMS45NzNsLTQuMTQ1IDIuNzY0Yy0uNTkyLjM5NC0xLjE4NC41OTItMS43MS41OTJjLS42NTggMC0xLjMxNi0uMzI5LTEuOTc0LS45MjFhMjAuNCAyMC40IDAgMCAxLTIuMzY4LTMuMDkyYTUxIDUxIDAgMCAxLTIuMDQtMy44ODJxLTcuNjk3IDkuMDgtMTkuMzQyIDkuMDc5Yy01LjUyNiAwLTkuOTM0LTEuNTc5LTEzLjE1OC00LjczN2MtMy4yMjMtMy4xNTgtNC44NjgtNy4zNjgtNC44NjgtMTIuNjMxYzAtNS41OTMgMS45NzQtMTAuMTMyIDUuOTg3LTEzLjU1M3M5LjM0Mi01LjEzMiAxNi4xMTgtNS4xMzJjMi4yMzcgMCA0LjU0LjE5OCA2Ljk3NC41MjdzNC45MzQuODU1IDcuNTY2IDEuNDQ3di00LjgwM2MwLTUtMS4wNTMtOC40ODctMy4wOTItMTAuNTI2Yy0yLjEwNi0yLjA0LTUuNjU4LTMuMDI2LTEwLjcyNC0zLjAyNmMtMi4zMDMgMC00LjY3MS4yNjMtNy4xMDUuODU1cy00LjgwMyAxLjMxNi03LjEwNiAyLjIzN2ExOSAxOSAwIDAgMS0yLjMwMi44NTVjLS40Ni4xMzItLjc5LjE5OC0xLjA1My4xOThjLS45MiAwLTEuMzgyLS42NTgtMS4zODItMi4wNHYtMy4yMjRjMC0xLjA1Mi4xMzItMS44NDIuNDYxLTIuMzAycy45MjEtLjkyMSAxLjg0Mi0xLjM4MnEzLjQ1NC0xLjc3NiA4LjI5LTIuOTZjMy4yMjMtLjg1NiA2LjY0NC0xLjI1IDEwLjI2My0xLjI1YzcuODI5IDAgMTMuNTUyIDEuNzc2IDE3LjIzNyA1LjMyOGMzLjYxOCAzLjU1MyA1LjQ2IDguOTQ4IDUuNDYgMTYuMTg1djIxLjMxNnptLTI2LjcxIDEwYzIuMTcgMCA0LjQwNy0uMzk1IDYuNzc2LTEuMTg1YzIuMzY4LS43ODkgNC40NzMtMi4yMzcgNi4yNS00LjIxYzEuMDUyLTEuMjUgMS44NDItMi42MzIgMi4yMzYtNC4yMTFzLjY1OC0zLjQ4Ny42NTgtNS43MjN2LTIuNzY0YTU1IDU1IDAgMCAwLTYuMDUyLTEuMTE4YTUwIDUwIDAgMCAwLTYuMTg1LS4zOTVjLTQuNDA4IDAtNy42MzEuODU2LTkuODAyIDIuNjMycy0zLjIyNCA0LjI3Ni0zLjIyNCA3LjU2NmMwIDMuMDkyLjc5IDUuMzk0IDIuNDM0IDYuOTczYzEuNTggMS42NDUgMy44ODIgMi40MzUgNi45MDggMi40MzVtNTIuODI4IDcuMTA1Yy0xLjE4NCAwLTEuOTc0LS4xOTgtMi41LS42NThjLS41MjYtLjM5NS0uOTg3LTEuMzE2LTEuMzgxLTIuNTY2bC0xNS40Ni01MC44NTVjLS4zOTYtMS4zMTYtLjU5My0yLjE3MS0uNTkzLTIuNjMyYzAtMS4wNTIuNTI2LTEuNjQ1IDEuNTc5LTEuNjQ1aDYuNDQ3YzEuMjUgMCAyLjEwNi4xOTggMi41NjYuNjU4Yy41MjYuMzk1LjkyMSAxLjMxNiAxLjMxNiAyLjU2NmwxMS4wNTIgNDMuNTUzbDEwLjI2NC00My41NTNjLjMyOS0xLjMxNi43MjMtMi4xNyAxLjI1LTIuNTY2Yy41MjYtLjM5NCAxLjQ0Ny0uNjU3IDIuNjMxLS42NTdoNS4yNjNjMS4yNSAwIDIuMTA2LjE5NyAyLjYzMi42NTdjLjUyNi4zOTUuOTg3IDEuMzE2IDEuMjUgMi41NjZsMTAuMzk1IDQ0LjA3OWwxMS4zODEtNDQuMDc5Yy4zOTUtMS4zMTYuODU2LTIuMTcgMS4zMTYtMi41NjZjLjUyNi0uMzk0IDEuMzgyLS42NTcgMi41NjYtLjY1N2g2LjExOGMxLjA1MyAwIDEuNjQ1LjUyNiAxLjY0NSAxLjY0NGMwIC4zMy0uMDY2LjY1OC0uMTMyIDEuMDUzYy0uMDY1LjM5NS0uMTk3LjkyLS40NiAxLjY0NWwtMTUuODU1IDUwLjg1NXEtLjU5MyAxLjk3NC0xLjM4MiAyLjU2NmMtLjUyNi4zOTQtMS4zODIuNjU4LTIuNS42NThoLTUuNjU4Yy0xLjI1IDAtMi4xMDUtLjE5OC0yLjYzMS0uNjU4Yy0uNTI3LS40NjEtLjk4Ny0xLjMxNi0xLjI1LTIuNjMybC0xMC4xOTgtNDIuNDM0bC0xMC4xMzEgNDIuMzY4Yy0uMzI5IDEuMzE2LS43MjQgMi4xNzEtMS4yNSAyLjYzMmMtLjUyNy40Ni0xLjQ0OC42NTgtMi42MzIuNjU4em04NC41NCAxLjc3NmMtMy40MjEgMC02Ljg0Mi0uMzk1LTEwLjEzMi0xLjE4NGMtMy4yODktLjc5LTUuODU1LTEuNjQ1LTcuNTY2LTIuNjMyYy0xLjA1Mi0uNTkyLTEuNzc2LTEuMjUtMi4wMzktMS44NDJhNC42NSA0LjY1IDAgMCAxLS4zOTUtMS44NDJ2LTMuMzU1YzAtMS4zODIuNTI2LTIuMDQgMS41MTMtMi4wNHEuNTkzIDAgMS4xODQuMTk4Yy4zOTUuMTMxLjk4Ny4zOTQgMS42NDUuNjU4YTM1LjggMzUuOCAwIDAgMCA3LjIzNyAyLjMwMmEzOS41IDM5LjUgMCAwIDAgNy44MjkuNzljNC4xNDUgMCA3LjM2OC0uNzI0IDkuNjA1LTIuMTcxYzIuMjM3LTEuNDQ4IDMuNDIxLTMuNTUzIDMuNDIxLTYuMjVjMC0xLjg0Mi0uNTkyLTMuMzU2LTEuNzc2LTQuNjA2cy0zLjQyMS0yLjM2OC02LjY0NS0zLjQyMWwtOS41MzktMi45NmMtNC44MDMtMS41MTMtOC4zNTYtMy43NS0xMC41MjctNi43MWMtMi4xNzEtMi44OTUtMy4yODktNi4xMi0zLjI4OS05LjU0cTAtNC4xNDQgMS43NzYtNy4zMDNjMS4xODQtMi4xMDUgMi43NjMtMy45NDcgNC43MzctNS4zOTRjMS45NzQtMS41MTQgNC4yMTEtMi42MzIgNi44NDItMy40MjJjMi42MzItLjc5IDUuMzk1LTEuMTE4IDguMjktMS4xMThjMS40NDcgMCAyLjk2LjA2NiA0LjQwOC4yNjNjMS41MTMuMTk3IDIuODk0LjQ2IDQuMjc2LjcyNGMxLjMxNi4zMjkgMi41NjYuNjU4IDMuNzUgMS4wNTNxMS43NzYuNTkxIDIuNzYzIDEuMTg0Yy45MjEuNTI2IDEuNTc5IDEuMDUyIDEuOTc0IDEuNjQ0cS41OTIuNzkuNTkyIDIuMTcydjMuMDkyYzAgMS4zODEtLjUyNiAyLjEwNS0xLjUxMyAyLjEwNWMtLjUyNyAwLTEuMzgyLS4yNjMtMi41LS43OXEtNS42MjUtMi41NjUtMTIuNjMyLTIuNTY1Yy0zLjc1IDAtNi43MS41OTItOC43NSAxLjg0MnMtMy4wOTIgMy4xNTgtMy4wOTIgNS44NTVjMCAxLjg0Mi42NTggMy40MjEgMS45NzQgNC42NzFzMy43NSAyLjUgNy4yMzcgMy42MThsOS4zNDIgMi45NmM0LjczNiAxLjUxNCA4LjE1OCAzLjYxOSAxMC4xOTcgNi4zMTdzMy4wMjYgNS43ODkgMy4wMjYgOS4yMWMwIDIuODI5LS41OTIgNS4zOTUtMS43MSA3LjYzMmMtMS4xODQgMi4yMzctMi43NjMgNC4yMS00LjgwMyA1Ljc4OWMtMi4wMzkgMS42NDUtNC40NzQgMi44MjktNy4zMDIgMy42ODVjLTIuOTYxLjkyMS02LjA1MyAxLjM4MS05LjQwOCAxLjM4MSIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIwNy44MzcgMTYyLjgxNmMtMjEuNjQ1IDE1Ljk4Ny01My4wOTIgMjQuNDc0LTgwLjEzMiAyNC40NzRjLTM3Ljg5NCAwLTcyLjA0LTE0LjAxNC05Ny44MjktMzcuMzAzYy0yLjA0LTEuODQyLS4xOTctNC4zNDIgMi4yMzctMi44OTVjMjcuODk1IDE2LjE4NCA2Mi4zMDMgMjUuOTg3IDk3Ljg5NSAyNS45ODdjMjQuMDEzIDAgNTAuMzk1LTUgNzQuNjcxLTE1LjI2M2MzLjYxOC0xLjY0NSA2LjcxIDIuMzY4IDMuMTU4IDUiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIxNi44NSAxNTIuNTUzYy0yLjc2My0zLjU1My0xOC4yODktMS43MTEtMjUuMzI5LS44NTZjLTIuMTA1LjI2NC0yLjQzNC0xLjU3OS0uNTI2LTIuOTZjMTIuMzY4LTguNjg0IDMyLjY5Ny02LjE4NCAzNS4wNjYtMy4yOWMyLjM2OCAyLjk2MS0uNjU4IDIzLjI5LTEyLjIzNyAzMy4wMjdjLTEuNzc3IDEuNTEzLTMuNDg3LjcyMy0yLjY5OC0xLjI1YzIuNjMyLTYuNTEzIDguNDg3LTIxLjE4NSA1LjcyNC0yNC42NzEiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=", - resources: 156, - added: "May 5, 2024", - }, - { - id: 6, - account: "aws", - provider_id: 740350143849, - connected: true, - group: "Management", - scan_status: "active", - scan_status_time: "3 minutes, 10 seconds", - last_scan: "Jul 17, 2024 11:46am UTC", - next_scan: "Jul 18, 2024 11:46am UTC", - provider_img: - "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgdmlld0JveD0iMCAwIDI1NiAyNTYiPjxnIGZpbGw9Im5vbmUiPjxyZWN0IHdpZHRoPSIyNTYiIGhlaWdodD0iMjU2IiBmaWxsPSIjZjRmMmVkIiByeD0iNjAiLz48cGF0aCBmaWxsPSIjMjUyZjNlIiBkPSJNODQuNzQ1IDExMS45NjFjMCAyLjQzNC4yNjMgNC40MDcuNzIzIDUuODU1YTM1IDM1IDAgMCAwIDIuMTA2IDQuNzM3Yy4zMjkuNTI2LjQ2IDEuMDUyLjQ2IDEuNTEzYzAgLjY1OC0uMzk1IDEuMzE2LTEuMjUgMS45NzNsLTQuMTQ1IDIuNzY0Yy0uNTkyLjM5NC0xLjE4NC41OTItMS43MS41OTJjLS42NTggMC0xLjMxNi0uMzI5LTEuOTc0LS45MjFhMjAuNCAyMC40IDAgMCAxLTIuMzY4LTMuMDkyYTUxIDUxIDAgMCAxLTIuMDQtMy44ODJxLTcuNjk3IDkuMDgtMTkuMzQyIDkuMDc5Yy01LjUyNiAwLTkuOTM0LTEuNTc5LTEzLjE1OC00LjczN2MtMy4yMjMtMy4xNTgtNC44NjgtNy4zNjgtNC44NjgtMTIuNjMxYzAtNS41OTMgMS45NzQtMTAuMTMyIDUuOTg3LTEzLjU1M3M5LjM0Mi01LjEzMiAxNi4xMTgtNS4xMzJjMi4yMzcgMCA0LjU0LjE5OCA2Ljk3NC41MjdzNC45MzQuODU1IDcuNTY2IDEuNDQ3di00LjgwM2MwLTUtMS4wNTMtOC40ODctMy4wOTItMTAuNTI2Yy0yLjEwNi0yLjA0LTUuNjU4LTMuMDI2LTEwLjcyNC0zLjAyNmMtMi4zMDMgMC00LjY3MS4yNjMtNy4xMDUuODU1cy00LjgwMyAxLjMxNi03LjEwNiAyLjIzN2ExOSAxOSAwIDAgMS0yLjMwMi44NTVjLS40Ni4xMzItLjc5LjE5OC0xLjA1My4xOThjLS45MiAwLTEuMzgyLS42NTgtMS4zODItMi4wNHYtMy4yMjRjMC0xLjA1Mi4xMzItMS44NDIuNDYxLTIuMzAycy45MjEtLjkyMSAxLjg0Mi0xLjM4MnEzLjQ1NC0xLjc3NiA4LjI5LTIuOTZjMy4yMjMtLjg1NiA2LjY0NC0xLjI1IDEwLjI2My0xLjI1YzcuODI5IDAgMTMuNTUyIDEuNzc2IDE3LjIzNyA1LjMyOGMzLjYxOCAzLjU1MyA1LjQ2IDguOTQ4IDUuNDYgMTYuMTg1djIxLjMxNnptLTI2LjcxIDEwYzIuMTcgMCA0LjQwNy0uMzk1IDYuNzc2LTEuMTg1YzIuMzY4LS43ODkgNC40NzMtMi4yMzcgNi4yNS00LjIxYzEuMDUyLTEuMjUgMS44NDItMi42MzIgMi4yMzYtNC4yMTFzLjY1OC0zLjQ4Ny42NTgtNS43MjN2LTIuNzY0YTU1IDU1IDAgMCAwLTYuMDUyLTEuMTE4YTUwIDUwIDAgMCAwLTYuMTg1LS4zOTVjLTQuNDA4IDAtNy42MzEuODU2LTkuODAyIDIuNjMycy0zLjIyNCA0LjI3Ni0zLjIyNCA3LjU2NmMwIDMuMDkyLjc5IDUuMzk0IDIuNDM0IDYuOTczYzEuNTggMS42NDUgMy44ODIgMi40MzUgNi45MDggMi40MzVtNTIuODI4IDcuMTA1Yy0xLjE4NCAwLTEuOTc0LS4xOTgtMi41LS42NThjLS41MjYtLjM5NS0uOTg3LTEuMzE2LTEuMzgxLTIuNTY2bC0xNS40Ni01MC44NTVjLS4zOTYtMS4zMTYtLjU5My0yLjE3MS0uNTkzLTIuNjMyYzAtMS4wNTIuNTI2LTEuNjQ1IDEuNTc5LTEuNjQ1aDYuNDQ3YzEuMjUgMCAyLjEwNi4xOTggMi41NjYuNjU4Yy41MjYuMzk1LjkyMSAxLjMxNiAxLjMxNiAyLjU2NmwxMS4wNTIgNDMuNTUzbDEwLjI2NC00My41NTNjLjMyOS0xLjMxNi43MjMtMi4xNyAxLjI1LTIuNTY2Yy41MjYtLjM5NCAxLjQ0Ny0uNjU3IDIuNjMxLS42NTdoNS4yNjNjMS4yNSAwIDIuMTA2LjE5NyAyLjYzMi42NTdjLjUyNi4zOTUuOTg3IDEuMzE2IDEuMjUgMi41NjZsMTAuMzk1IDQ0LjA3OWwxMS4zODEtNDQuMDc5Yy4zOTUtMS4zMTYuODU2LTIuMTcgMS4zMTYtMi41NjZjLjUyNi0uMzk0IDEuMzgyLS42NTcgMi41NjYtLjY1N2g2LjExOGMxLjA1MyAwIDEuNjQ1LjUyNiAxLjY0NSAxLjY0NGMwIC4zMy0uMDY2LjY1OC0uMTMyIDEuMDUzYy0uMDY1LjM5NS0uMTk3LjkyLS40NiAxLjY0NWwtMTUuODU1IDUwLjg1NXEtLjU5MyAxLjk3NC0xLjM4MiAyLjU2NmMtLjUyNi4zOTQtMS4zODIuNjU4LTIuNS42NThoLTUuNjU4Yy0xLjI1IDAtMi4xMDUtLjE5OC0yLjYzMS0uNjU4Yy0uNTI3LS40NjEtLjk4Ny0xLjMxNi0xLjI1LTIuNjMybC0xMC4xOTgtNDIuNDM0bC0xMC4xMzEgNDIuMzY4Yy0uMzI5IDEuMzE2LS43MjQgMi4xNzEtMS4yNSAyLjYzMmMtLjUyNy40Ni0xLjQ0OC42NTgtMi42MzIuNjU4em04NC41NCAxLjc3NmMtMy40MjEgMC02Ljg0Mi0uMzk1LTEwLjEzMi0xLjE4NGMtMy4yODktLjc5LTUuODU1LTEuNjQ1LTcuNTY2LTIuNjMyYy0xLjA1Mi0uNTkyLTEuNzc2LTEuMjUtMi4wMzktMS44NDJhNC42NSA0LjY1IDAgMCAxLS4zOTUtMS44NDJ2LTMuMzU1YzAtMS4zODIuNTI2LTIuMDQgMS41MTMtMi4wNHEuNTkzIDAgMS4xODQuMTk4Yy4zOTUuMTMxLjk4Ny4zOTQgMS42NDUuNjU4YTM1LjggMzUuOCAwIDAgMCA3LjIzNyAyLjMwMmEzOS41IDM5LjUgMCAwIDAgNy44MjkuNzljNC4xNDUgMCA3LjM2OC0uNzI0IDkuNjA1LTIuMTcxYzIuMjM3LTEuNDQ4IDMuNDIxLTMuNTUzIDMuNDIxLTYuMjVjMC0xLjg0Mi0uNTkyLTMuMzU2LTEuNzc2LTQuNjA2cy0zLjQyMS0yLjM2OC02LjY0NS0zLjQyMWwtOS41MzktMi45NmMtNC44MDMtMS41MTMtOC4zNTYtMy43NS0xMC41MjctNi43MWMtMi4xNzEtMi44OTUtMy4yODktNi4xMi0zLjI4OS05LjU0cTAtNC4xNDQgMS43NzYtNy4zMDNjMS4xODQtMi4xMDUgMi43NjMtMy45NDcgNC43MzctNS4zOTRjMS45NzQtMS41MTQgNC4yMTEtMi42MzIgNi44NDItMy40MjJjMi42MzItLjc5IDUuMzk1LTEuMTE4IDguMjktMS4xMThjMS40NDcgMCAyLjk2LjA2NiA0LjQwOC4yNjNjMS41MTMuMTk3IDIuODk0LjQ2IDQuMjc2LjcyNGMxLjMxNi4zMjkgMi41NjYuNjU4IDMuNzUgMS4wNTNxMS43NzYuNTkxIDIuNzYzIDEuMTg0Yy45MjEuNTI2IDEuNTc5IDEuMDUyIDEuOTc0IDEuNjQ0cS41OTIuNzkuNTkyIDIuMTcydjMuMDkyYzAgMS4zODEtLjUyNiAyLjEwNS0xLjUxMyAyLjEwNWMtLjUyNyAwLTEuMzgyLS4yNjMtMi41LS43OXEtNS42MjUtMi41NjUtMTIuNjMyLTIuNTY1Yy0zLjc1IDAtNi43MS41OTItOC43NSAxLjg0MnMtMy4wOTIgMy4xNTgtMy4wOTIgNS44NTVjMCAxLjg0Mi42NTggMy40MjEgMS45NzQgNC42NzFzMy43NSAyLjUgNy4yMzcgMy42MThsOS4zNDIgMi45NmM0LjczNiAxLjUxNCA4LjE1OCAzLjYxOSAxMC4xOTcgNi4zMTdzMy4wMjYgNS43ODkgMy4wMjYgOS4yMWMwIDIuODI5LS41OTIgNS4zOTUtMS43MSA3LjYzMmMtMS4xODQgMi4yMzctMi43NjMgNC4yMS00LjgwMyA1Ljc4OWMtMi4wMzkgMS42NDUtNC40NzQgMi44MjktNy4zMDIgMy42ODVjLTIuOTYxLjkyMS02LjA1MyAxLjM4MS05LjQwOCAxLjM4MSIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIwNy44MzcgMTYyLjgxNmMtMjEuNjQ1IDE1Ljk4Ny01My4wOTIgMjQuNDc0LTgwLjEzMiAyNC40NzRjLTM3Ljg5NCAwLTcyLjA0LTE0LjAxNC05Ny44MjktMzcuMzAzYy0yLjA0LTEuODQyLS4xOTctNC4zNDIgMi4yMzctMi44OTVjMjcuODk1IDE2LjE4NCA2Mi4zMDMgMjUuOTg3IDk3Ljg5NSAyNS45ODdjMjQuMDEzIDAgNTAuMzk1LTUgNzQuNjcxLTE1LjI2M2MzLjYxOC0xLjY0NSA2LjcxIDIuMzY4IDMuMTU4IDUiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIxNi44NSAxNTIuNTUzYy0yLjc2My0zLjU1My0xOC4yODktMS43MTEtMjUuMzI5LS44NTZjLTIuMTA1LjI2NC0yLjQzNC0xLjU3OS0uNTI2LTIuOTZjMTIuMzY4LTguNjg0IDMyLjY5Ny02LjE4NCAzNS4wNjYtMy4yOWMyLjM2OCAyLjk2MS0uNjU4IDIzLjI5LTEyLjIzNyAzMy4wMjdjLTEuNzc3IDEuNTEzLTMuNDg3LjcyMy0yLjY5OC0xLjI1YzIuNjMyLTYuNTEzIDguNDg3LTIxLjE4NSA1LjcyNC0yNC42NzEiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=", - resources: 98, - added: "Mar 22, 2024", - }, - { - id: 7, - account: "aws", - provider_id: 740350143850, - connected: false, - group: "Design", - scan_status: "paused", - scan_status_time: "12 minutes, 5 seconds", - last_scan: "Jul 17, 2024 10:46am UTC", - next_scan: "Jul 18, 2024 10:46am UTC", - provider_img: - "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgdmlld0JveD0iMCAwIDI1NiAyNTYiPjxnIGZpbGw9Im5vbmUiPjxyZWN0IHdpZHRoPSIyNTYiIGhlaWdodD0iMjU2IiBmaWxsPSIjZjRmMmVkIiByeD0iNjAiLz48cGF0aCBmaWxsPSIjMjUyZjNlIiBkPSJNODQuNzQ1IDExMS45NjFjMCAyLjQzNC4yNjMgNC40MDcuNzIzIDUuODU1YTM1IDM1IDAgMCAwIDIuMTA2IDQuNzM3Yy4zMjkuNTI2LjQ2IDEuMDUyLjQ2IDEuNTEzYzAgLjY1OC0uMzk1IDEuMzE2LTEuMjUgMS45NzNsLTQuMTQ1IDIuNzY0Yy0uNTkyLjM5NC0xLjE4NC41OTItMS43MS41OTJjLS42NTggMC0xLjMxNi0uMzI5LTEuOTc0LS45MjFhMjAuNCAyMC40IDAgMCAxLTIuMzY4LTMuMDkyYTUxIDUxIDAgMCAxLTIuMDQtMy44ODJxLTcuNjk3IDkuMDgtMTkuMzQyIDkuMDc5Yy01LjUyNiAwLTkuOTM0LTEuNTc5LTEzLjE1OC00LjczN2MtMy4yMjMtMy4xNTgtNC44NjgtNy4zNjgtNC44NjgtMTIuNjMxYzAtNS41OTMgMS45NzQtMTAuMTMyIDUuOTg3LTEzLjU1M3M5LjM0Mi01LjEzMiAxNi4xMTgtNS4xMzJjMi4yMzcgMCA0LjU0LjE5OCA2Ljk3NC41MjdzNC45MzQuODU1IDcuNTY2IDEuNDQ3di00LjgwM2MwLTUtMS4wNTMtOC40ODctMy4wOTItMTAuNTI2Yy0yLjEwNi0yLjA0LTUuNjU4LTMuMDI2LTEwLjcyNC0zLjAyNmMtMi4zMDMgMC00LjY3MS4yNjMtNy4xMDUuODU1cy00LjgwMyAxLjMxNi03LjEwNiAyLjIzN2ExOSAxOSAwIDAgMS0yLjMwMi44NTVjLS40Ni4xMzItLjc5LjE5OC0xLjA1My4xOThjLS45MiAwLTEuMzgyLS42NTgtMS4zODItMi4wNHYtMy4yMjRjMC0xLjA1Mi4xMzItMS44NDIuNDYxLTIuMzAycy45MjEtLjkyMSAxLjg0Mi0xLjM4MnEzLjQ1NC0xLjc3NiA4LjI5LTIuOTZjMy4yMjMtLjg1NiA2LjY0NC0xLjI1IDEwLjI2My0xLjI1YzcuODI5IDAgMTMuNTUyIDEuNzc2IDE3LjIzNyA1LjMyOGMzLjYxOCAzLjU1MyA1LjQ2IDguOTQ4IDUuNDYgMTYuMTg1djIxLjMxNnptLTI2LjcxIDEwYzIuMTcgMCA0LjQwNy0uMzk1IDYuNzc2LTEuMTg1YzIuMzY4LS43ODkgNC40NzMtMi4yMzcgNi4yNS00LjIxYzEuMDUyLTEuMjUgMS44NDItMi42MzIgMi4yMzYtNC4yMTFzLjY1OC0zLjQ4Ny42NTgtNS43MjN2LTIuNzY0YTU1IDU1IDAgMCAwLTYuMDUyLTEuMTE4YTUwIDUwIDAgMCAwLTYuMTg1LS4zOTVjLTQuNDA4IDAtNy42MzEuODU2LTkuODAyIDIuNjMycy0zLjIyNCA0LjI3Ni0zLjIyNCA3LjU2NmMwIDMuMDkyLjc5IDUuMzk0IDIuNDM0IDYuOTczYzEuNTggMS42NDUgMy44ODIgMi40MzUgNi45MDggMi40MzVtNTIuODI4IDcuMTA1Yy0xLjE4NCAwLTEuOTc0LS4xOTgtMi41LS42NThjLS41MjYtLjM5NS0uOTg3LTEuMzE2LTEuMzgxLTIuNTY2bC0xNS40Ni01MC44NTVjLS4zOTYtMS4zMTYtLjU5My0yLjE3MS0uNTkzLTIuNjMyYzAtMS4wNTIuNTI2LTEuNjQ1IDEuNTc5LTEuNjQ1aDYuNDQ3YzEuMjUgMCAyLjEwNi4xOTggMi41NjYuNjU4Yy41MjYuMzk1LjkyMSAxLjMxNiAxLjMxNiAyLjU2NmwxMS4wNTIgNDMuNTUzbDEwLjI2NC00My41NTNjLjMyOS0xLjMxNi43MjMtMi4xNyAxLjI1LTIuNTY2Yy41MjYtLjM5NCAxLjQ0Ny0uNjU3IDIuNjMxLS42NTdoNS4yNjNjMS4yNSAwIDIuMTA2LjE5NyAyLjYzMi42NTdjLjUyNi4zOTUuOTg3IDEuMzE2IDEuMjUgMi41NjZsMTAuMzk1IDQ0LjA3OWwxMS4zODEtNDQuMDc5Yy4zOTUtMS4zMTYuODU2LTIuMTcgMS4zMTYtMi41NjZjLjUyNi0uMzk0IDEuMzgyLS42NTcgMi41NjYtLjY1N2g2LjExOGMxLjA1MyAwIDEuNjQ1LjUyNiAxLjY0NSAxLjY0NGMwIC4zMy0uMDY2LjY1OC0uMTMyIDEuMDUzYy0uMDY1LjM5NS0uMTk3LjkyLS40NiAxLjY0NWwtMTUuODU1IDUwLjg1NXEtLjU5MyAxLjk3NC0xLjM4MiAyLjU2NmMtLjUyNi4zOTQtMS4zODIuNjU4LTIuNS42NThoLTUuNjU4Yy0xLjI1IDAtMi4xMDUtLjE5OC0yLjYzMS0uNjU4Yy0uNTI3LS40NjEtLjk4Ny0xLjMxNi0xLjI1LTIuNjMybC0xMC4xOTgtNDIuNDM0bC0xMC4xMzEgNDIuMzY4Yy0uMzI5IDEuMzE2LS43MjQgMi4xNzEtMS4yNSAyLjYzMmMtLjUyNy40Ni0xLjQ0OC42NTgtMi42MzIuNjU4em04NC41NCAxLjc3NmMtMy40MjEgMC02Ljg0Mi0uMzk1LTEwLjEzMi0xLjE4NGMtMy4yODktLjc5LTUuODU1LTEuNjQ1LTcuNTY2LTIuNjMyYy0xLjA1Mi0uNTkyLTEuNzc2LTEuMjUtMi4wMzktMS44NDJhNC42NSA0LjY1IDAgMCAxLS4zOTUtMS44NDJ2LTMuMzU1YzAtMS4zODIuNTI2LTIuMDQgMS41MTMtMi4wNHEuNTkzIDAgMS4xODQuMTk4Yy4zOTUuMTMxLjk4Ny4zOTQgMS42NDUuNjU4YTM1LjggMzUuOCAwIDAgMCA3LjIzNyAyLjMwMmEzOS41IDM5LjUgMCAwIDAgNy44MjkuNzljNC4xNDUgMCA3LjM2OC0uNzI0IDkuNjA1LTIuMTcxYzIuMjM3LTEuNDQ4IDMuNDIxLTMuNTUzIDMuNDIxLTYuMjVjMC0xLjg0Mi0uNTkyLTMuMzU2LTEuNzc2LTQuNjA2cy0zLjQyMS0yLjM2OC02LjY0NS0zLjQyMWwtOS41MzktMi45NmMtNC44MDMtMS41MTMtOC4zNTYtMy43NS0xMC41MjctNi43MWMtMi4xNzEtMi44OTUtMy4yODktNi4xMi0zLjI4OS05LjU0cTAtNC4xNDQgMS43NzYtNy4zMDNjMS4xODQtMi4xMDUgMi43NjMtMy45NDcgNC43MzctNS4zOTRjMS45NzQtMS41MTQgNC4yMTEtMi42MzIgNi44NDItMy40MjJjMi42MzItLjc5IDUuMzk1LTEuMTE4IDguMjktMS4xMThjMS40NDcgMCAyLjk2LjA2NiA0LjQwOC4yNjNjMS41MTMuMTk3IDIuODk0LjQ2IDQuMjc2LjcyNGMxLjMxNi4zMjkgMi41NjYuNjU4IDMuNzUgMS4wNTNxMS43NzYuNTkxIDIuNzYzIDEuMTg0Yy45MjEuNTI2IDEuNTc5IDEuMDUyIDEuOTc0IDEuNjQ0cS41OTIuNzkuNTkyIDIuMTcydjMuMDkyYzAgMS4zODEtLjUyNiAyLjEwNS0xLjUxMyAyLjEwNWMtLjUyNyAwLTEuMzgyLS4yNjMtMi41LS43OXEtNS42MjUtMi41NjUtMTIuNjMyLTIuNTY1Yy0zLjc1IDAtNi43MS41OTItOC43NSAxLjg0MnMtMy4wOTIgMy4xNTgtMy4wOTIgNS44NTVjMCAxLjg0Mi42NTggMy40MjEgMS45NzQgNC42NzFzMy43NSAyLjUgNy4yMzcgMy42MThsOS4zNDIgMi45NmM0LjczNiAxLjUxNCA4LjE1OCAzLjYxOSAxMC4xOTcgNi4zMTdzMy4wMjYgNS43ODkgMy4wMjYgOS4yMWMwIDIuODI5LS41OTIgNS4zOTUtMS43MSA3LjYzMmMtMS4xODQgMi4yMzctMi43NjMgNC4yMS00LjgwMyA1Ljc4OWMtMi4wMzkgMS42NDUtNC40NzQgMi44MjktNy4zMDIgMy42ODVjLTIuOTYxLjkyMS02LjA1MyAxLjM4MS05LjQwOCAxLjM4MSIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIwNy44MzcgMTYyLjgxNmMtMjEuNjQ1IDE1Ljk4Ny01My4wOTIgMjQuNDc0LTgwLjEzMiAyNC40NzRjLTM3Ljg5NCAwLTcyLjA0LTE0LjAxNC05Ny44MjktMzcuMzAzYy0yLjA0LTEuODQyLS4xOTctNC4zNDIgMi4yMzctMi44OTVjMjcuODk1IDE2LjE4NCA2Mi4zMDMgMjUuOTg3IDk3Ljg5NSAyNS45ODdjMjQuMDEzIDAgNTAuMzk1LTUgNzQuNjcxLTE1LjI2M2MzLjYxOC0xLjY0NSA2LjcxIDIuMzY4IDMuMTU4IDUiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIxNi44NSAxNTIuNTUzYy0yLjc2My0zLjU1My0xOC4yODktMS43MTEtMjUuMzI5LS44NTZjLTIuMTA1LjI2NC0yLjQzNC0xLjU3OS0uNTI2LTIuOTZjMTIuMzY4LTguNjg0IDMyLjY5Ny02LjE4NCAzNS4wNjYtMy4yOWMyLjM2OCAyLjk2MS0uNjU4IDIzLjI5LTEyLjIzNyAzMy4wMjdjLTEuNzc3IDEuNTEzLTMuNDg3LjcyMy0yLjY5OC0xLjI1YzIuNjMyLTYuNTEzIDguNDg3LTIxLjE4NSA1LjcyNC0yNC42NzEiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=", - resources: 76, - added: "Feb 28, 2024", - }, - { - id: 8, - account: "aws", - provider_id: 740350143851, - connected: true, - group: "HR", - scan_status: "active", - scan_status_time: "8 minutes, 20 seconds", - last_scan: "Jul 17, 2024 9:46am UTC", - next_scan: "Jul 18, 2024 9:46am UTC", - provider_img: - "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgdmlld0JveD0iMCAwIDI1NiAyNTYiPjxnIGZpbGw9Im5vbmUiPjxyZWN0IHdpZHRoPSIyNTYiIGhlaWdodD0iMjU2IiBmaWxsPSIjZjRmMmVkIiByeD0iNjAiLz48cGF0aCBmaWxsPSIjMjUyZjNlIiBkPSJNODQuNzQ1IDExMS45NjFjMCAyLjQzNC4yNjMgNC40MDcuNzIzIDUuODU1YTM1IDM1IDAgMCAwIDIuMTA2IDQuNzM3Yy4zMjkuNTI2LjQ2IDEuMDUyLjQ2IDEuNTEzYzAgLjY1OC0uMzk1IDEuMzE2LTEuMjUgMS45NzNsLTQuMTQ1IDIuNzY0Yy0uNTkyLjM5NC0xLjE4NC41OTItMS43MS41OTJjLS42NTggMC0xLjMxNi0uMzI5LTEuOTc0LS45MjFhMjAuNCAyMC40IDAgMCAxLTIuMzY4LTMuMDkyYTUxIDUxIDAgMCAxLTIuMDQtMy44ODJxLTcuNjk3IDkuMDgtMTkuMzQyIDkuMDc5Yy01LjUyNiAwLTkuOTM0LTEuNTc5LTEzLjE1OC00LjczN2MtMy4yMjMtMy4xNTgtNC44NjgtNy4zNjgtNC44NjgtMTIuNjMxYzAtNS41OTMgMS45NzQtMTAuMTMyIDUuOTg3LTEzLjU1M3M5LjM0Mi01LjEzMiAxNi4xMTgtNS4xMzJjMi4yMzcgMCA0LjU0LjE5OCA2Ljk3NC41MjdzNC45MzQuODU1IDcuNTY2IDEuNDQ3di00LjgwM2MwLTUtMS4wNTMtOC40ODctMy4wOTItMTAuNTI2Yy0yLjEwNi0yLjA0LTUuNjU4LTMuMDI2LTEwLjcyNC0zLjAyNmMtMi4zMDMgMC00LjY3MS4yNjMtNy4xMDUuODU1cy00LjgwMyAxLjMxNi03LjEwNiAyLjIzN2ExOSAxOSAwIDAgMS0yLjMwMi44NTVjLS40Ni4xMzItLjc5LjE5OC0xLjA1My4xOThjLS45MiAwLTEuMzgyLS42NTgtMS4zODItMi4wNHYtMy4yMjRjMC0xLjA1Mi4xMzItMS44NDIuNDYxLTIuMzAycy45MjEtLjkyMSAxLjg0Mi0xLjM4MnEzLjQ1NC0xLjc3NiA4LjI5LTIuOTZjMy4yMjMtLjg1NiA2LjY0NC0xLjI1IDEwLjI2My0xLjI1YzcuODI5IDAgMTMuNTUyIDEuNzc2IDE3LjIzNyA1LjMyOGMzLjYxOCAzLjU1MyA1LjQ2IDguOTQ4IDUuNDYgMTYuMTg1djIxLjMxNnptLTI2LjcxIDEwYzIuMTcgMCA0LjQwNy0uMzk1IDYuNzc2LTEuMTg1YzIuMzY4LS43ODkgNC40NzMtMi4yMzcgNi4yNS00LjIxYzEuMDUyLTEuMjUgMS44NDItMi42MzIgMi4yMzYtNC4yMTFzLjY1OC0zLjQ4Ny42NTgtNS43MjN2LTIuNzY0YTU1IDU1IDAgMCAwLTYuMDUyLTEuMTE4YTUwIDUwIDAgMCAwLTYuMTg1LS4zOTVjLTQuNDA4IDAtNy42MzEuODU2LTkuODAyIDIuNjMycy0zLjIyNCA0LjI3Ni0zLjIyNCA3LjU2NmMwIDMuMDkyLjc5IDUuMzk0IDIuNDM0IDYuOTczYzEuNTggMS42NDUgMy44ODIgMi40MzUgNi45MDggMi40MzVtNTIuODI4IDcuMTA1Yy0xLjE4NCAwLTEuOTc0LS4xOTgtMi41LS42NThjLS41MjYtLjM5NS0uOTg3LTEuMzE2LTEuMzgxLTIuNTY2bC0xNS40Ni01MC44NTVjLS4zOTYtMS4zMTYtLjU5My0yLjE3MS0uNTkzLTIuNjMyYzAtMS4wNTIuNTI2LTEuNjQ1IDEuNTc5LTEuNjQ1aDYuNDQ3YzEuMjUgMCAyLjEwNi4xOTggMi41NjYuNjU4Yy41MjYuMzk1LjkyMSAxLjMxNiAxLjMxNiAyLjU2NmwxMS4wNTIgNDMuNTUzbDEwLjI2NC00My41NTNjLjMyOS0xLjMxNi43MjMtMi4xNyAxLjI1LTIuNTY2Yy41MjYtLjM5NCAxLjQ0Ny0uNjU3IDIuNjMxLS42NTdoNS4yNjNjMS4yNSAwIDIuMTA2LjE5NyAyLjYzMi42NTdjLjUyNi4zOTUuOTg3IDEuMzE2IDEuMjUgMi41NjZsMTAuMzk1IDQ0LjA3OWwxMS4zODEtNDQuMDc5Yy4zOTUtMS4zMTYuODU2LTIuMTcgMS4zMTYtMi41NjZjLjUyNi0uMzk0IDEuMzgyLS42NTcgMi41NjYtLjY1N2g2LjExOGMxLjA1MyAwIDEuNjQ1LjUyNiAxLjY0NSAxLjY0NGMwIC4zMy0uMDY2LjY1OC0uMTMyIDEuMDUzYy0uMDY1LjM5NS0uMTk3LjkyLS40NiAxLjY0NWwtMTUuODU1IDUwLjg1NXEtLjU5MyAxLjk3NC0xLjM4MiAyLjU2NmMtLjUyNi4zOTQtMS4zODIuNjU4LTIuNS42NThoLTUuNjU4Yy0xLjI1IDAtMi4xMDUtLjE5OC0yLjYzMS0uNjU4Yy0uNTI3LS40NjEtLjk4Ny0xLjMxNi0xLjI1LTIuNjMybC0xMC4xOTgtNDIuNDM0bC0xMC4xMzEgNDIuMzY4Yy0uMzI5IDEuMzE2LS43MjQgMi4xNzEtMS4yNSAyLjYzMmMtLjUyNy40Ni0xLjQ0OC42NTgtMi42MzIuNjU4em04NC41NCAxLjc3NmMtMy40MjEgMC02Ljg0Mi0uMzk1LTEwLjEzMi0xLjE4NGMtMy4yODktLjc5LTUuODU1LTEuNjQ1LTcuNTY2LTIuNjMyYy0xLjA1Mi0uNTkyLTEuNzc2LTEuMjUtMi4wMzktMS44NDJhNC42NSA0LjY1IDAgMCAxLS4zOTUtMS44NDJ2LTMuMzU1YzAtMS4zODIuNTI2LTIuMDQgMS41MTMtMi4wNHEuNTkzIDAgMS4xODQuMTk4Yy4zOTUuMTMxLjk4Ny4zOTQgMS42NDUuNjU4YTM1LjggMzUuOCAwIDAgMCA3LjIzNyAyLjMwMmEzOS41IDM5LjUgMCAwIDAgNy44MjkuNzljNC4xNDUgMCA3LjM2OC0uNzI0IDkuNjA1LTIuMTcxYzIuMjM3LTEuNDQ4IDMuNDIxLTMuNTUzIDMuNDIxLTYuMjVjMC0xLjg0Mi0uNTkyLTMuMzU2LTEuNzc2LTQuNjA2cy0zLjQyMS0yLjM2OC02LjY0NS0zLjQyMWwtOS41MzktMi45NmMtNC44MDMtMS41MTMtOC4zNTYtMy43NS0xMC41MjctNi43MWMtMi4xNzEtMi44OTUtMy4yODktNi4xMi0zLjI4OS05LjU0cTAtNC4xNDQgMS43NzYtNy4zMDNjMS4xODQtMi4xMDUgMi43NjMtMy45NDcgNC43MzctNS4zOTRjMS45NzQtMS41MTQgNC4yMTEtMi42MzIgNi44NDItMy40MjJjMi42MzItLjc5IDUuMzk1LTEuMTE4IDguMjktMS4xMThjMS40NDcgMCAyLjk2LjA2NiA0LjQwOC4yNjNjMS41MTMuMTk3IDIuODk0LjQ2IDQuMjc2LjcyNGMxLjMxNi4zMjkgMi41NjYuNjU4IDMuNzUgMS4wNTNxMS43NzYuNTkxIDIuNzYzIDEuMTg0Yy45MjEuNTI2IDEuNTc5IDEuMDUyIDEuOTc0IDEuNjQ0cS41OTIuNzkuNTkyIDIuMTcydjMuMDkyYzAgMS4zODEtLjUyNiAyLjEwNS0xLjUxMyAyLjEwNWMtLjUyNyAwLTEuMzgyLS4yNjMtMi41LS43OXEtNS42MjUtMi41NjUtMTIuNjMyLTIuNTY1Yy0zLjc1IDAtNi43MS41OTItOC43NSAxLjg0MnMtMy4wOTIgMy4xNTgtMy4wOTIgNS44NTVjMCAxLjg0Mi42NTggMy40MjEgMS45NzQgNC42NzFzMy43NSAyLjUgNy4yMzcgMy42MThsOS4zNDIgMi45NmM0LjczNiAxLjUxNCA4LjE1OCAzLjYxOSAxMC4xOTcgNi4zMTdzMy4wMjYgNS43ODkgMy4wMjYgOS4yMWMwIDIuODI5LS41OTIgNS4zOTUtMS43MSA3LjYzMmMtMS4xODQgMi4yMzctMi43NjMgNC4yMS00LjgwMyA1Ljc4OWMtMi4wMzkgMS42NDUtNC40NzQgMi44MjktNy4zMDIgMy42ODVjLTIuOTYxLjkyMS02LjA1MyAxLjM4MS05LjQwOCAxLjM4MSIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIwNy44MzcgMTYyLjgxNmMtMjEuNjQ1IDE1Ljk4Ny01My4wOTIgMjQuNDc0LTgwLjEzMiAyNC40NzRjLTM3Ljg5NCAwLTcyLjA0LTE0LjAxNC05Ny44MjktMzcuMzAzYy0yLjA0LTEuODQyLS4xOTctNC4zNDIgMi4yMzctMi44OTVjMjcuODk1IDE2LjE4NCA2Mi4zMDMgMjUuOTg3IDk3Ljg5NSAyNS45ODdjMjQuMDEzIDAgNTAuMzk1LTUgNzQuNjcxLTE1LjI2M2MzLjYxOC0xLjY0NSA2LjcxIDIuMzY4IDMuMTU4IDUiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIxNi44NSAxNTIuNTUzYy0yLjc2My0zLjU1My0xOC4yODktMS43MTEtMjUuMzI5LS44NTZjLTIuMTA1LjI2NC0yLjQzNC0xLjU3OS0uNTI2LTIuOTZjMTIuMzY4LTguNjg0IDMyLjY5Ny02LjE4NCAzNS4wNjYtMy4yOWMyLjM2OCAyLjk2MS0uNjU4IDIzLjI5LTEyLjIzNyAzMy4wMjdjLTEuNzc3IDEuNTEzLTMuNDg3LjcyMy0yLjY5OC0xLjI1YzIuNjMyLTYuNTEzIDguNDg3LTIxLjE4NSA1LjcyNC0yNC42NzEiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=", - resources: 110, - added: "Apr 14, 2024", - }, - { - id: 9, - account: "aws", - provider_id: 740350143852, - connected: true, - group: "Finance", - scan_status: "completed", - scan_status_time: "20 minutes, 30 seconds", - last_scan: "Jul 17, 2024 8:46am UTC", - next_scan: "Jul 18, 2024 8:46am UTC", - provider_img: - "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgdmlld0JveD0iMCAwIDI1NiAyNTYiPjxnIGZpbGw9Im5vbmUiPjxyZWN0IHdpZHRoPSIyNTYiIGhlaWdodD0iMjU2IiBmaWxsPSIjZjRmMmVkIiByeD0iNjAiLz48cGF0aCBmaWxsPSIjMjUyZjNlIiBkPSJNODQuNzQ1IDExMS45NjFjMCAyLjQzNC4yNjMgNC40MDcuNzIzIDUuODU1YTM1IDM1IDAgMCAwIDIuMTA2IDQuNzM3Yy4zMjkuNTI2LjQ2IDEuMDUyLjQ2IDEuNTEzYzAgLjY1OC0uMzk1IDEuMzE2LTEuMjUgMS45NzNsLTQuMTQ1IDIuNzY0Yy0uNTkyLjM5NC0xLjE4NC41OTItMS43MS41OTJjLS42NTggMC0xLjMxNi0uMzI5LTEuOTc0LS45MjFhMjAuNCAyMC40IDAgMCAxLTIuMzY4LTMuMDkyYTUxIDUxIDAgMCAxLTIuMDQtMy44ODJxLTcuNjk3IDkuMDgtMTkuMzQyIDkuMDc5Yy01LjUyNiAwLTkuOTM0LTEuNTc5LTEzLjE1OC00LjczN2MtMy4yMjMtMy4xNTgtNC44NjgtNy4zNjgtNC44NjgtMTIuNjMxYzAtNS41OTMgMS45NzQtMTAuMTMyIDUuOTg3LTEzLjU1M3M5LjM0Mi01LjEzMiAxNi4xMTgtNS4xMzJjMi4yMzcgMCA0LjU0LjE5OCA2Ljk3NC41MjdzNC45MzQuODU1IDcuNTY2IDEuNDQ3di00LjgwM2MwLTUtMS4wNTMtOC40ODctMy4wOTItMTAuNTI2Yy0yLjEwNi0yLjA0LTUuNjU4LTMuMDI2LTEwLjcyNC0zLjAyNmMtMi4zMDMgMC00LjY3MS4yNjMtNy4xMDUuODU1cy00LjgwMyAxLjMxNi03LjEwNiAyLjIzN2ExOSAxOSAwIDAgMS0yLjMwMi44NTVjLS40Ni4xMzItLjc5LjE5OC0xLjA1My4xOThjLS45MiAwLTEuMzgyLS42NTgtMS4zODItMi4wNHYtMy4yMjRjMC0xLjA1Mi4xMzItMS44NDIuNDYxLTIuMzAycy45MjEtLjkyMSAxLjg0Mi0xLjM4MnEzLjQ1NC0xLjc3NiA4LjI5LTIuOTZjMy4yMjMtLjg1NiA2LjY0NC0xLjI1IDEwLjI2My0xLjI1YzcuODI5IDAgMTMuNTUyIDEuNzc2IDE3LjIzNyA1LjMyOGMzLjYxOCAzLjU1MyA1LjQ2IDguOTQ4IDUuNDYgMTYuMTg1djIxLjMxNnptLTI2LjcxIDEwYzIuMTcgMCA0LjQwNy0uMzk1IDYuNzc2LTEuMTg1YzIuMzY4LS43ODkgNC40NzMtMi4yMzcgNi4yNS00LjIxYzEuMDUyLTEuMjUgMS44NDItMi42MzIgMi4yMzYtNC4yMTFzLjY1OC0zLjQ4Ny42NTgtNS43MjN2LTIuNzY0YTU1IDU1IDAgMCAwLTYuMDUyLTEuMTE4YTUwIDUwIDAgMCAwLTYuMTg1LS4zOTVjLTQuNDA4IDAtNy42MzEuODU2LTkuODAyIDIuNjMycy0zLjIyNCA0LjI3Ni0zLjIyNCA3LjU2NmMwIDMuMDkyLjc5IDUuMzk0IDIuNDM0IDYuOTczYzEuNTggMS42NDUgMy44ODIgMi40MzUgNi45MDggMi40MzVtNTIuODI4IDcuMTA1Yy0xLjE4NCAwLTEuOTc0LS4xOTgtMi41LS42NThjLS41MjYtLjM5NS0uOTg3LTEuMzE2LTEuMzgxLTIuNTY2bC0xNS40Ni01MC44NTVjLS4zOTYtMS4zMTYtLjU5My0yLjE3MS0uNTkzLTIuNjMyYzAtMS4wNTIuNTI2LTEuNjQ1IDEuNTc5LTEuNjQ1aDYuNDQ3YzEuMjUgMCAyLjEwNi4xOTggMi41NjYuNjU4Yy41MjYuMzk1LjkyMSAxLjMxNiAxLjMxNiAyLjU2NmwxMS4wNTIgNDMuNTUzbDEwLjI2NC00My41NTNjLjMyOS0xLjMxNi43MjMtMi4xNyAxLjI1LTIuNTY2Yy41MjYtLjM5NCAxLjQ0Ny0uNjU3IDIuNjMxLS42NTdoNS4yNjNjMS4yNSAwIDIuMTA2LjE5NyAyLjYzMi42NTdjLjUyNi4zOTUuOTg3IDEuMzE2IDEuMjUgMi41NjZsMTAuMzk1IDQ0LjA3OWwxMS4zODEtNDQuMDc5Yy4zOTUtMS4zMTYuODU2LTIuMTcgMS4zMTYtMi41NjZjLjUyNi0uMzk0IDEuMzgyLS42NTcgMi41NjYtLjY1N2g2LjExOGMxLjA1MyAwIDEuNjQ1LjUyNiAxLjY0NSAxLjY0NGMwIC4zMy0uMDY2LjY1OC0uMTMyIDEuMDUzYy0uMDY1LjM5NS0uMTk3LjkyLS40NiAxLjY0NWwtMTUuODU1IDUwLjg1NXEtLjU5MyAxLjk3NC0xLjM4MiAyLjU2NmMtLjUyNi4zOTQtMS4zODIuNjU4LTIuNS42NThoLTUuNjU4Yy0xLjI1IDAtMi4xMDUtLjE5OC0yLjYzMS0uNjU4Yy0uNTI3LS40NjEtLjk4Ny0xLjMxNi0xLjI1LTIuNjMybC0xMC4xOTgtNDIuNDM0bC0xMC4xMzEgNDIuMzY4Yy0uMzI5IDEuMzE2LS43MjQgMi4xNzEtMS4yNSAyLjYzMmMtLjUyNy40Ni0xLjQ0OC42NTgtMi42MzIuNjU4em04NC41NCAxLjc3NmMtMy40MjEgMC02Ljg0Mi0uMzk1LTEwLjEzMi0xLjE4NGMtMy4yODktLjc5LTUuODU1LTEuNjQ1LTcuNTY2LTIuNjMyYy0xLjA1Mi0uNTkyLTEuNzc2LTEuMjUtMi4wMzktMS44NDJhNC42NSA0LjY1IDAgMCAxLS4zOTUtMS44NDJ2LTMuMzU1YzAtMS4zODIuNTI2LTIuMDQgMS41MTMtMi4wNHEuNTkzIDAgMS4xODQuMTk4Yy4zOTUuMTMxLjk4Ny4zOTQgMS42NDUuNjU4YTM1LjggMzUuOCAwIDAgMCA3LjIzNyAyLjMwMmEzOS41IDM5LjUgMCAwIDAgNy44MjkuNzljNC4xNDUgMCA3LjM2OC0uNzI0IDkuNjA1LTIuMTcxYzIuMjM3LTEuNDQ4IDMuNDIxLTMuNTUzIDMuNDIxLTYuMjVjMC0xLjg0Mi0uNTkyLTMuMzU2LTEuNzc2LTQuNjA2cy0zLjQyMS0yLjM2OC02LjY0NS0zLjQyMWwtOS41MzktMi45NmMtNC44MDMtMS41MTMtOC4zNTYtMy43NS0xMC41MjctNi43MWMtMi4xNzEtMi44OTUtMy4yODktNi4xMi0zLjI4OS05LjU0cTAtNC4xNDQgMS43NzYtNy4zMDNjMS4xODQtMi4xMDUgMi43NjMtMy45NDcgNC43MzctNS4zOTRjMS45NzQtMS41MTQgNC4yMTEtMi42MzIgNi44NDItMy40MjJjMi42MzItLjc5IDUuMzk1LTEuMTE4IDguMjktMS4xMThjMS40NDcgMCAyLjk2LjA2NiA0LjQwOC4yNjNjMS41MTMuMTk3IDIuODk0LjQ2IDQuMjc2LjcyNGMxLjMxNi4zMjkgMi41NjYuNjU4IDMuNzUgMS4wNTNxMS43NzYuNTkxIDIuNzYzIDEuMTg0Yy45MjEuNTI2IDEuNTc5IDEuMDUyIDEuOTc0IDEuNjQ0cS41OTIuNzkuNTkyIDIuMTcydjMuMDkyYzAgMS4zODEtLjUyNiAyLjEwNS0xLjUxMyAyLjEwNWMtLjUyNyAwLTEuMzgyLS4yNjMtMi41LS43OXEtNS42MjUtMi41NjUtMTIuNjMyLTIuNTY1Yy0zLjc1IDAtNi43MS41OTItOC43NSAxLjg0MnMtMy4wOTIgMy4xNTgtMy4wOTIgNS44NTVjMCAxLjg0Mi42NTggMy40MjEgMS45NzQgNC42NzFzMy43NSAyLjUgNy4yMzcgMy42MThsOS4zNDIgMi45NmM0LjczNiAxLjUxNCA4LjE1OCAzLjYxOSAxMC4xOTcgNi4zMTdzMy4wMjYgNS43ODkgMy4wMjYgOS4yMWMwIDIuODI5LS41OTIgNS4zOTUtMS43MSA3LjYzMmMtMS4xODQgMi4yMzctMi43NjMgNC4yMS00LjgwMyA1Ljc4OWMtMi4wMzkgMS42NDUtNC40NzQgMi44MjktNy4zMDIgMy42ODVjLTIuOTYxLjkyMS02LjA1MyAxLjM4MS05LjQwOCAxLjM4MSIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIwNy44MzcgMTYyLjgxNmMtMjEuNjQ1IDE1Ljk4Ny01My4wOTIgMjQuNDc0LTgwLjEzMiAyNC40NzRjLTM3Ljg5NCAwLTcyLjA0LTE0LjAxNC05Ny44MjktMzcuMzAzYy0yLjA0LTEuODQyLS4xOTctNC4zNDIgMi4yMzctMi44OTVjMjcuODk1IDE2LjE4NCA2Mi4zMDMgMjUuOTg3IDk3Ljg5NSAyNS45ODdjMjQuMDEzIDAgNTAuMzk1LTUgNzQuNjcxLTE1LjI2M2MzLjYxOC0xLjY0NSA2LjcxIDIuMzY4IDMuMTU4IDUiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIxNi44NSAxNTIuNTUzYy0yLjc2My0zLjU1My0xOC4yODktMS43MTEtMjUuMzI5LS44NTZjLTIuMTA1LjI2NC0yLjQzNC0xLjU3OS0uNTI2LTIuOTZjMTIuMzY4LTguNjg0IDMyLjY5Ny02LjE4NCAzNS4wNjYtMy4yOWMyLjM2OCAyLjk2MS0uNjU4IDIzLjI5LTEyLjIzNyAzMy4wMjdjLTEuNzc3IDEuNTEzLTMuNDg3LjcyMy0yLjY5OC0xLjI1YzIuNjMyLTYuNTEzIDguNDg3LTIxLjE4NSA1LjcyNC0yNC42NzEiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=", - resources: 145, - added: "Jan 5, 2024", - }, - { - id: 10, - account: "aws", - provider_id: 740350143853, - connected: true, - group: "Operations", - scan_status: "active", - scan_status_time: "6 minutes, 50 seconds", - last_scan: "Jul 17, 2024 7:46am UTC", - next_scan: "Jul 18, 2024 7:46am UTC", - provider_img: - "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgdmlld0JveD0iMCAwIDI1NiAyNTYiPjxnIGZpbGw9Im5vbmUiPjxyZWN0IHdpZHRoPSIyNTYiIGhlaWdodD0iMjU2IiBmaWxsPSIjZjRmMmVkIiByeD0iNjAiLz48cGF0aCBmaWxsPSIjMjUyZjNlIiBkPSJNODQuNzQ1IDExMS45NjFjMCAyLjQzNC4yNjMgNC40MDcuNzIzIDUuODU1YTM1IDM1IDAgMCAwIDIuMTA2IDQuNzM3Yy4zMjkuNTI2LjQ2IDEuMDUyLjQ2IDEuNTEzYzAgLjY1OC0uMzk1IDEuMzE2LTEuMjUgMS45NzNsLTQuMTQ1IDIuNzY0Yy0uNTkyLjM5NC0xLjE4NC41OTItMS43MS41OTJjLS42NTggMC0xLjMxNi0uMzI5LTEuOTc0LS45MjFhMjAuNCAyMC40IDAgMCAxLTIuMzY4LTMuMDkyYTUxIDUxIDAgMCAxLTIuMDQtMy44ODJxLTcuNjk3IDkuMDgtMTkuMzQyIDkuMDc5Yy01LjUyNiAwLTkuOTM0LTEuNTc5LTEzLjE1OC00LjczN2MtMy4yMjMtMy4xNTgtNC44NjgtNy4zNjgtNC44NjgtMTIuNjMxYzAtNS41OTMgMS45NzQtMTAuMTMyIDUuOTg3LTEzLjU1M3M5LjM0Mi01LjEzMiAxNi4xMTgtNS4xMzJjMi4yMzcgMCA0LjU0LjE5OCA2Ljk3NC41MjdzNC45MzQuODU1IDcuNTY2IDEuNDQ3di00LjgwM2MwLTUtMS4wNTMtOC40ODctMy4wOTItMTAuNTI2Yy0yLjEwNi0yLjA0LTUuNjU4LTMuMDI2LTEwLjcyNC0zLjAyNmMtMi4zMDMgMC00LjY3MS4yNjMtNy4xMDUuODU1cy00LjgwMyAxLjMxNi03LjEwNiAyLjIzN2ExOSAxOSAwIDAgMS0yLjMwMi44NTVjLS40Ni4xMzItLjc5LjE5OC0xLjA1My4xOThjLS45MiAwLTEuMzgyLS42NTgtMS4zODItMi4wNHYtMy4yMjRjMC0xLjA1Mi4xMzItMS44NDIuNDYxLTIuMzAycy45MjEtLjkyMSAxLjg0Mi0xLjM4MnEzLjQ1NC0xLjc3NiA4LjI5LTIuOTZjMy4yMjMtLjg1NiA2LjY0NC0xLjI1IDEwLjI2My0xLjI1YzcuODI5IDAgMTMuNTUyIDEuNzc2IDE3LjIzNyA1LjMyOGMzLjYxOCAzLjU1MyA1LjQ2IDguOTQ4IDUuNDYgMTYuMTg1djIxLjMxNnptLTI2LjcxIDEwYzIuMTcgMCA0LjQwNy0uMzk1IDYuNzc2LTEuMTg1YzIuMzY4LS43ODkgNC40NzMtMi4yMzcgNi4yNS00LjIxYzEuMDUyLTEuMjUgMS44NDItMi42MzIgMi4yMzYtNC4yMTFzLjY1OC0zLjQ4Ny42NTgtNS43MjN2LTIuNzY0YTU1IDU1IDAgMCAwLTYuMDUyLTEuMTE4YTUwIDUwIDAgMCAwLTYuMTg1LS4zOTVjLTQuNDA4IDAtNy42MzEuODU2LTkuODAyIDIuNjMycy0zLjIyNCA0LjI3Ni0zLjIyNCA3LjU2NmMwIDMuMDkyLjc5IDUuMzk0IDIuNDM0IDYuOTczYzEuNTggMS42NDUgMy44ODIgMi40MzUgNi45MDggMi40MzVtNTIuODI4IDcuMTA1Yy0xLjE4NCAwLTEuOTc0LS4xOTgtMi41LS42NThjLS41MjYtLjM5NS0uOTg3LTEuMzE2LTEuMzgxLTIuNTY2bC0xNS40Ni01MC44NTVjLS4zOTYtMS4zMTYtLjU5My0yLjE3MS0uNTkzLTIuNjMyYzAtMS4wNTIuNTI2LTEuNjQ1IDEuNTc5LTEuNjQ1aDYuNDQ3YzEuMjUgMCAyLjEwNi4xOTggMi41NjYuNjU4Yy41MjYuMzk1LjkyMSAxLjMxNiAxLjMxNiAyLjU2NmwxMS4wNTIgNDMuNTUzbDEwLjI2NC00My41NTNjLjMyOS0xLjMxNi43MjMtMi4xNyAxLjI1LTIuNTY2Yy41MjYtLjM5NCAxLjQ0Ny0uNjU3IDIuNjMxLS42NTdoNS4yNjNjMS4yNSAwIDIuMTA2LjE5NyAyLjYzMi42NTdjLjUyNi4zOTUuOTg3IDEuMzE2IDEuMjUgMi41NjZsMTAuMzk1IDQ0LjA3OWwxMS4zODEtNDQuMDc5Yy4zOTUtMS4zMTYuODU2LTIuMTcgMS4zMTYtMi41NjZjLjUyNi0uMzk0IDEuMzgyLS42NTcgMi41NjYtLjY1N2g2LjExOGMxLjA1MyAwIDEuNjQ1LjUyNiAxLjY0NSAxLjY0NGMwIC4zMy0uMDY2LjY1OC0uMTMyIDEuMDUzYy0uMDY1LjM5NS0uMTk3LjkyLS40NiAxLjY0NWwtMTUuODU1IDUwLjg1NXEtLjU5MyAxLjk3NC0xLjM4MiAyLjU2NmMtLjUyNi4zOTQtMS4zODIuNjU4LTIuNS42NThoLTUuNjU4Yy0xLjI1IDAtMi4xMDUtLjE5OC0yLjYzMS0uNjU4Yy0uNTI3LS40NjEtLjk4Ny0xLjMxNi0xLjI1LTIuNjMybC0xMC4xOTgtNDIuNDM0bC0xMC4xMzEgNDIuMzY4Yy0uMzI5IDEuMzE2LS43MjQgMi4xNzEtMS4yNSAyLjYzMmMtLjUyNy40Ni0xLjQ0OC42NTgtMi42MzIuNjU4em04NC41NCAxLjc3NmMtMy40MjEgMC02Ljg0Mi0uMzk1LTEwLjEzMi0xLjE4NGMtMy4yODktLjc5LTUuODU1LTEuNjQ1LTcuNTY2LTIuNjMyYy0xLjA1Mi0uNTkyLTEuNzc2LTEuMjUtMi4wMzktMS44NDJhNC42NSA0LjY1IDAgMCAxLS4zOTUtMS44NDJ2LTMuMzU1YzAtMS4zODIuNTI2LTIuMDQgMS41MTMtMi4wNHEuNTkzIDAgMS4xODQuMTk4Yy4zOTUuMTMxLjk4Ny4zOTQgMS42NDUuNjU4YTM1LjggMzUuOCAwIDAgMCA3LjIzNyAyLjMwMmEzOS41IDM5LjUgMCAwIDAgNy44MjkuNzljNC4xNDUgMCA3LjM2OC0uNzI0IDkuNjA1LTIuMTcxYzIuMjM3LTEuNDQ4IDMuNDIxLTMuNTUzIDMuNDIxLTYuMjVjMC0xLjg0Mi0uNTkyLTMuMzU2LTEuNzc2LTQuNjA2cy0zLjQyMS0yLjM2OC02LjY0NS0zLjQyMWwtOS41MzktMi45NmMtNC44MDMtMS41MTMtOC4zNTYtMy43NS0xMC41MjctNi43MWMtMi4xNzEtMi44OTUtMy4yODktNi4xMi0zLjI4OS05LjU0cTAtNC4xNDQgMS43NzYtNy4zMDNjMS4xODQtMi4xMDUgMi43NjMtMy45NDcgNC43MzctNS4zOTRjMS45NzQtMS41MTQgNC4yMTEtMi42MzIgNi44NDItMy40MjJjMi42MzItLjc5IDUuMzk1LTEuMTE4IDguMjktMS4xMThjMS40NDcgMCAyLjk2LjA2NiA0LjQwOC4yNjNjMS41MTMuMTk3IDIuODk0LjQ2IDQuMjc2LjcyNGMxLjMxNi4zMjkgMi41NjYuNjU4IDMuNzUgMS4wNTNxMS43NzYuNTkxIDIuNzYzIDEuMTg0Yy45MjEuNTI2IDEuNTc5IDEuMDUyIDEuOTc0IDEuNjQ0cS41OTIuNzkuNTkyIDIuMTcydjMuMDkyYzAgMS4zODEtLjUyNiAyLjEwNS0xLjUxMyAyLjEwNWMtLjUyNyAwLTEuMzgyLS4yNjMtMi41LS43OXEtNS42MjUtMi41NjUtMTIuNjMyLTIuNTY1Yy0zLjc1IDAtNi43MS41OTItOC43NSAxLjg0MnMtMy4wOTIgMy4xNTgtMy4wOTIgNS44NTVjMCAxLjg0Mi42NTggMy40MjEgMS45NzQgNC42NzFzMy43NSAyLjUgNy4yMzcgMy42MThsOS4zNDIgMi45NmM0LjczNiAxLjUxNCA4LjE1OCAzLjYxOSAxMC4xOTcgNi4zMTdzMy4wMjYgNS43ODkgMy4wMjYgOS4yMWMwIDIuODI5LS41OTIgNS4zOTUtMS43MSA3LjYzMmMtMS4xODQgMi4yMzctMi43NjMgNC4yMS00LjgwMyA1Ljc4OWMtMi4wMzkgMS42NDUtNC40NzQgMi44MjktNy4zMDIgMy42ODVjLTIuOTYxLjkyMS02LjA1MyAxLjM4MS05LjQwOCAxLjM4MSIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIwNy44MzcgMTYyLjgxNmMtMjEuNjQ1IDE1Ljk4Ny01My4wOTIgMjQuNDc0LTgwLjEzMiAyNC40NzRjLTM3Ljg5NCAwLTcyLjA0LTE0LjAxNC05Ny44MjktMzcuMzAzYy0yLjA0LTEuODQyLS4xOTctNC4zNDIgMi4yMzctMi44OTVjMjcuODk1IDE2LjE4NCA2Mi4zMDMgMjUuOTg3IDk3Ljg5NSAyNS45ODdjMjQuMDEzIDAgNTAuMzk1LTUgNzQuNjcxLTE1LjI2M2MzLjYxOC0xLjY0NSA2LjcxIDIuMzY4IDMuMTU4IDUiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIxNi44NSAxNTIuNTUzYy0yLjc2My0zLjU1My0xOC4yODktMS43MTEtMjUuMzI5LS44NTZjLTIuMTA1LjI2NC0yLjQzNC0xLjU3OS0uNTI2LTIuOTZjMTIuMzY4LTguNjg0IDMyLjY5Ny02LjE4NCAzNS4wNjYtMy4yOWMyLjM2OCAyLjk2MS0uNjU4IDIzLjI5LTEyLjIzNyAzMy4wMjdjLTEuNzc3IDEuNTEzLTMuNDg3LjcyMy0yLjY5OC0xLjI1YzIuNjMyLTYuNTEzIDguNDg3LTIxLjE4NSA1LjcyNC0yNC42NzEiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=", - resources: 132, - added: "Mar 8, 2024", - }, - { - id: 11, - account: "aws", - provider_id: 740350143854, - connected: true, - group: "Development", - scan_status: "active", - scan_status_time: "4 minutes, 15 seconds", - last_scan: "Jul 17, 2024 6:46am UTC", - next_scan: "Jul 18, 2024 6:46am UTC", - provider_img: - "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgdmlld0JveD0iMCAwIDI1NiAyNTYiPjxnIGZpbGw9Im5vbmUiPjxyZWN0IHdpZHRoPSIyNTYiIGhlaWdodD0iMjU2IiBmaWxsPSIjZjRmMmVkIiByeD0iNjAiLz48cGF0aCBmaWxsPSIjMjUyZjNlIiBkPSJNODQuNzQ1IDExMS45NjFjMCAyLjQzNC4yNjMgNC40MDcuNzIzIDUuODU1YTM1IDM1IDAgMCAwIDIuMTA2IDQuNzM3Yy4zMjkuNTI2LjQ2IDEuMDUyLjQ2IDEuNTEzYzAgLjY1OC0uMzk1IDEuMzE2LTEuMjUgMS45NzNsLTQuMTQ1IDIuNzY0Yy0uNTkyLjM5NC0xLjE4NC41OTItMS43MS41OTJjLS42NTggMC0xLjMxNi0uMzI5LTEuOTc0LS45MjFhMjAuNCAyMC40IDAgMCAxLTIuMzY4LTMuMDkyYTUxIDUxIDAgMCAxLTIuMDQtMy44ODJxLTcuNjk3IDkuMDgtMTkuMzQyIDkuMDc5Yy01LjUyNiAwLTkuOTM0LTEuNTc5LTEzLjE1OC00LjczN2MtMy4yMjMtMy4xNTgtNC44NjgtNy4zNjgtNC44NjgtMTIuNjMxYzAtNS41OTMgMS45NzQtMTAuMTMyIDUuOTg3LTEzLjU1M3M5LjM0Mi01LjEzMiAxNi4xMTgtNS4xMzJjMi4yMzcgMCA0LjU0LjE5OCA2Ljk3NC41MjdzNC45MzQuODU1IDcuNTY2IDEuNDQ3di00LjgwM2MwLTUtMS4wNTMtOC40ODctMy4wOTItMTAuNTI2Yy0yLjEwNi0yLjA0LTUuNjU4LTMuMDI2LTEwLjcyNC0zLjAyNmMtMi4zMDMgMC00LjY3MS4yNjMtNy4xMDUuODU1cy00LjgwMyAxLjMxNi03LjEwNiAyLjIzN2ExOSAxOSAwIDAgMS0yLjMwMi44NTVjLS40Ni4xMzItLjc5LjE5OC0xLjA1My4xOThjLS45MiAwLTEuMzgyLS42NTgtMS4zODItMi4wNHYtMy4yMjRjMC0xLjA1Mi4xMzItMS44NDIuNDYxLTIuMzAycy45MjEtLjkyMSAxLjg0Mi0xLjM4MnEzLjQ1NC0xLjc3NiA4LjI5LTIuOTZjMy4yMjMtLjg1NiA2LjY0NC0xLjI1IDEwLjI2My0xLjI1YzcuODI5IDAgMTMuNTUyIDEuNzc2IDE3LjIzNyA1LjMyOGMzLjYxOCAzLjU1MyA1LjQ2IDguOTQ4IDUuNDYgMTYuMTg1djIxLjMxNnptLTI2LjcxIDEwYzIuMTcgMCA0LjQwNy0uMzk1IDYuNzc2LTEuMTg1YzIuMzY4LS43ODkgNC40NzMtMi4yMzcgNi4yNS00LjIxYzEuMDUyLTEuMjUgMS44NDItMi42MzIgMi4yMzYtNC4yMTFzLjY1OC0zLjQ4Ny42NTgtNS43MjN2LTIuNzY0YTU1IDU1IDAgMCAwLTYuMDUyLTEuMTE4YTUwIDUwIDAgMCAwLTYuMTg1LS4zOTVjLTQuNDA4IDAtNy42MzEuODU2LTkuODAyIDIuNjMycy0zLjIyNCA0LjI3Ni0zLjIyNCA3LjU2NmMwIDMuMDkyLjc5IDUuMzk0IDIuNDM0IDYuOTczYzEuNTggMS42NDUgMy44ODIgMi40MzUgNi45MDggMi40MzVtNTIuODI4IDcuMTA1Yy0xLjE4NCAwLTEuOTc0LS4xOTgtMi41LS42NThjLS41MjYtLjM5NS0uOTg3LTEuMzE2LTEuMzgxLTIuNTY2bC0xNS40Ni01MC44NTVjLS4zOTYtMS4zMTYtLjU5My0yLjE3MS0uNTkzLTIuNjMyYzAtMS4wNTIuNTI2LTEuNjQ1IDEuNTc5LTEuNjQ1aDYuNDQ3YzEuMjUgMCAyLjEwNi4xOTggMi41NjYuNjU4Yy41MjYuMzk1LjkyMSAxLjMxNiAxLjMxNiAyLjU2NmwxMS4wNTIgNDMuNTUzbDEwLjI2NC00My41NTNjLjMyOS0xLjMxNi43MjMtMi4xNyAxLjI1LTIuNTY2Yy41MjYtLjM5NCAxLjQ0Ny0uNjU3IDIuNjMxLS42NTdoNS4yNjNjMS4yNSAwIDIuMTA2LjE5NyAyLjYzMi42NTdjLjUyNi4zOTUuOTg3IDEuMzE2IDEuMjUgMi41NjZsMTAuMzk1IDQ0LjA3OWwxMS4zODEtNDQuMDc5Yy4zOTUtMS4zMTYuODU2LTIuMTcgMS4zMTYtMi41NjZjLjUyNi0uMzk0IDEuMzgyLS42NTcgMi41NjYtLjY1N2g2LjExOGMxLjA1MyAwIDEuNjQ1LjUyNiAxLjY0NSAxLjY0NGMwIC4zMy0uMDY2LjY1OC0uMTMyIDEuMDUzYy0uMDY1LjM5NS0uMTk3LjkyLS40NiAxLjY0NWwtMTUuODU1IDUwLjg1NXEtLjU5MyAxLjk3NC0xLjM4MiAyLjU2NmMtLjUyNi4zOTQtMS4zODIuNjU4LTIuNS42NThoLTUuNjU4Yy0xLjI1IDAtMi4xMDUtLjE5OC0yLjYzMS0uNjU4Yy0uNTI3LS40NjEtLjk4Ny0xLjMxNi0xLjI1LTIuNjMybC0xMC4xOTgtNDIuNDM0bC0xMC4xMzEgNDIuMzY4Yy0uMzI5IDEuMzE2LS43MjQgMi4xNzEtMS4yNSAyLjYzMmMtLjUyNy40Ni0xLjQ0OC42NTgtMi42MzIuNjU4em04NC41NCAxLjc3NmMtMy40MjEgMC02Ljg0Mi0uMzk1LTEwLjEzMi0xLjE4NGMtMy4yODktLjc5LTUuODU1LTEuNjQ1LTcuNTY2LTIuNjMyYy0xLjA1Mi0uNTkyLTEuNzc2LTEuMjUtMi4wMzktMS44NDJhNC42NSA0LjY1IDAgMCAxLS4zOTUtMS44NDJ2LTMuMzU1YzAtMS4zODIuNTI2LTIuMDQgMS41MTMtMi4wNHEuNTkzIDAgMS4xODQuMTk4Yy4zOTUuMTMxLjk4Ny4zOTQgMS42NDUuNjU4YTM1LjggMzUuOCAwIDAgMCA3LjIzNyAyLjMwMmEzOS41IDM5LjUgMCAwIDAgNy44MjkuNzljNC4xNDUgMCA3LjM2OC0uNzI0IDkuNjA1LTIuMTcxYzIuMjM3LTEuNDQ4IDMuNDIxLTMuNTUzIDMuNDIxLTYuMjVjMC0xLjg0Mi0uNTkyLTMuMzU2LTEuNzc2LTQuNjA2cy0zLjQyMS0yLjM2OC02LjY0NS0zLjQyMWwtOS41MzktMi45NmMtNC44MDMtMS41MTMtOC4zNTYtMy43NS0xMC41MjctNi43MWMtMi4xNzEtMi44OTUtMy4yODktNi4xMi0zLjI4OS05LjU0cTAtNC4xNDQgMS43NzYtNy4zMDNjMS4xODQtMi4xMDUgMi43NjMtMy45NDcgNC43MzctNS4zOTRjMS45NzQtMS41MTQgNC4yMTEtMi42MzIgNi44NDItMy40MjJjMi42MzItLjc5IDUuMzk1LTEuMTE4IDguMjktMS4xMThjMS40NDcgMCAyLjk2LjA2NiA0LjQwOC4yNjNjMS41MTMuMTk3IDIuODk0LjQ2IDQuMjc2LjcyNGMxLjMxNi4zMjkgMi41NjYuNjU4IDMuNzUgMS4wNTNxMS43NzYuNTkxIDIuNzYzIDEuMTg0Yy45MjEuNTI2IDEuNTc5IDEuMDUyIDEuOTc0IDEuNjQ0cS41OTIuNzkuNTkyIDIuMTcydjMuMDkyYzAgMS4zODEtLjUyNiAyLjEwNS0xLjUxMyAyLjEwNWMtLjUyNyAwLTEuMzgyLS4yNjMtMi41LS43OXEtNS42MjUtMi41NjUtMTIuNjMyLTIuNTY1Yy0zLjc1IDAtNi43MS41OTItOC43NSAxLjg0MnMtMy4wOTIgMy4xNTgtMy4wOTIgNS44NTVjMCAxLjg0Mi42NTggMy40MjEgMS45NzQgNC42NzFzMy43NSAyLjUgNy4yMzcgMy42MThsOS4zNDIgMi45NmM0LjczNiAxLjUxNCA4LjE1OCAzLjYxOSAxMC4xOTcgNi4zMTdzMy4wMjYgNS43ODkgMy4wMjYgOS4yMWMwIDIuODI5LS41OTIgNS4zOTUtMS43MSA3LjYzMmMtMS4xODQgMi4yMzctMi43NjMgNC4yMS00LjgwMyA1Ljc4OWMtMi4wMzkgMS42NDUtNC40NzQgMi44MjktNy4zMDIgMy42ODVjLTIuOTYxLjkyMS02LjA1MyAxLjM4MS05LjQwOCAxLjM4MSIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIwNy44MzcgMTYyLjgxNmMtMjEuNjQ1IDE1Ljk4Ny01My4wOTIgMjQuNDc0LTgwLjEzMiAyNC40NzRjLTM3Ljg5NCAwLTcyLjA0LTE0LjAxNC05Ny44MjktMzcuMzAzYy0yLjA0LTEuODQyLS4xOTctNC4zNDIgMi4yMzctMi44OTVjMjcuODk1IDE2LjE4NCA2Mi4zMDMgMjUuOTg3IDk3Ljg5NSAyNS45ODdjMjQuMDEzIDAgNTAuMzk1LTUgNzQuNjcxLTE1LjI2M2MzLjYxOC0xLjY0NSA2LjcxIDIuMzY4IDMuMTU4IDUiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIxNi44NSAxNTIuNTUzYy0yLjc2My0zLjU1My0xOC4yODktMS43MTEtMjUuMzI5LS44NTZjLTIuMTA1LjI2NC0yLjQzNC0xLjU3OS0uNTI2LTIuOTZjMTIuMzY4LTguNjg0IDMyLjY5Ny02LjE4NCAzNS4wNjYtMy4yOWMyLjM2OCAyLjk2MS0uNjU4IDIzLjI5LTEyLjIzNyAzMy4wMjdjLTEuNzc3IDEuNTEzLTMuNDg3LjcyMy0yLjY5OC0xLjI1YzIuNjMyLTYuNTEzIDguNDg3LTIxLjE4NSA1LjcyNC0yNC42NzEiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=", - resources: 121, - added: "Apr 21, 2024", - }, - { - id: 12, - account: "aws", - provider_id: 740350143855, - connected: false, - group: "Product", - scan_status: "paused", - scan_status_time: "11 minutes, 40 seconds", - last_scan: "Jul 17, 2024 5:46am UTC", - next_scan: "Jul 18, 2024 5:46am UTC", - provider_img: - "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgdmlld0JveD0iMCAwIDI1NiAyNTYiPjxnIGZpbGw9Im5vbmUiPjxyZWN0IHdpZHRoPSIyNTYiIGhlaWdodD0iMjU2IiBmaWxsPSIjZjRmMmVkIiByeD0iNjAiLz48cGF0aCBmaWxsPSIjMjUyZjNlIiBkPSJNODQuNzQ1IDExMS45NjFjMCAyLjQzNC4yNjMgNC40MDcuNzIzIDUuODU1YTM1IDM1IDAgMCAwIDIuMTA2IDQuNzM3Yy4zMjkuNTI2LjQ2IDEuMDUyLjQ2IDEuNTEzYzAgLjY1OC0uMzk1IDEuMzE2LTEuMjUgMS45NzNsLTQuMTQ1IDIuNzY0Yy0uNTkyLjM5NC0xLjE4NC41OTItMS43MS41OTJjLS42NTggMC0xLjMxNi0uMzI5LTEuOTc0LS45MjFhMjAuNCAyMC40IDAgMCAxLTIuMzY4LTMuMDkyYTUxIDUxIDAgMCAxLTIuMDQtMy44ODJxLTcuNjk3IDkuMDgtMTkuMzQyIDkuMDc5Yy01LjUyNiAwLTkuOTM0LTEuNTc5LTEzLjE1OC00LjczN2MtMy4yMjMtMy4xNTgtNC44NjgtNy4zNjgtNC44NjgtMTIuNjMxYzAtNS41OTMgMS45NzQtMTAuMTMyIDUuOTg3LTEzLjU1M3M5LjM0Mi01LjEzMiAxNi4xMTgtNS4xMzJjMi4yMzcgMCA0LjU0LjE5OCA2Ljk3NC41MjdzNC45MzQuODU1IDcuNTY2IDEuNDQ3di00LjgwM2MwLTUtMS4wNTMtOC40ODctMy4wOTItMTAuNTI2Yy0yLjEwNi0yLjA0LTUuNjU4LTMuMDI2LTEwLjcyNC0zLjAyNmMtMi4zMDMgMC00LjY3MS4yNjMtNy4xMDUuODU1cy00LjgwMyAxLjMxNi03LjEwNiAyLjIzN2ExOSAxOSAwIDAgMS0yLjMwMi44NTVjLS40Ni4xMzItLjc5LjE5OC0xLjA1My4xOThjLS45MiAwLTEuMzgyLS42NTgtMS4zODItMi4wNHYtMy4yMjRjMC0xLjA1Mi4xMzItMS44NDIuNDYxLTIuMzAycy45MjEtLjkyMSAxLjg0Mi0xLjM4MnEzLjQ1NC0xLjc3NiA4LjI5LTIuOTZjMy4yMjMtLjg1NiA2LjY0NC0xLjI1IDEwLjI2My0xLjI1YzcuODI5IDAgMTMuNTUyIDEuNzc2IDE3LjIzNyA1LjMyOGMzLjYxOCAzLjU1MyA1LjQ2IDguOTQ4IDUuNDYgMTYuMTg1djIxLjMxNnptLTI2LjcxIDEwYzIuMTcgMCA0LjQwNy0uMzk1IDYuNzc2LTEuMTg1YzIuMzY4LS43ODkgNC40NzMtMi4yMzcgNi4yNS00LjIxYzEuMDUyLTEuMjUgMS44NDItMi42MzIgMi4yMzYtNC4yMTFzLjY1OC0zLjQ4Ny42NTgtNS43MjN2LTIuNzY0YTU1IDU1IDAgMCAwLTYuMDUyLTEuMTE4YTUwIDUwIDAgMCAwLTYuMTg1LS4zOTVjLTQuNDA4IDAtNy42MzEuODU2LTkuODAyIDIuNjMycy0zLjIyNCA0LjI3Ni0zLjIyNCA3LjU2NmMwIDMuMDkyLjc5IDUuMzk0IDIuNDM0IDYuOTczYzEuNTggMS42NDUgMy44ODIgMi40MzUgNi45MDggMi40MzVtNTIuODI4IDcuMTA1Yy0xLjE4NCAwLTEuOTc0LS4xOTgtMi41LS42NThjLS41MjYtLjM5NS0uOTg3LTEuMzE2LTEuMzgxLTIuNTY2bC0xNS40Ni01MC44NTVjLS4zOTYtMS4zMTYtLjU5My0yLjE3MS0uNTkzLTIuNjMyYzAtMS4wNTIuNTI2LTEuNjQ1IDEuNTc5LTEuNjQ1aDYuNDQ3YzEuMjUgMCAyLjEwNi4xOTggMi41NjYuNjU4Yy41MjYuMzk1LjkyMSAxLjMxNiAxLjMxNiAyLjU2NmwxMS4wNTIgNDMuNTUzbDEwLjI2NC00My41NTNjLjMyOS0xLjMxNi43MjMtMi4xNyAxLjI1LTIuNTY2Yy41MjYtLjM5NCAxLjQ0Ny0uNjU3IDIuNjMxLS42NTdoNS4yNjNjMS4yNSAwIDIuMTA2LjE5NyAyLjYzMi42NTdjLjUyNi4zOTUuOTg3IDEuMzE2IDEuMjUgMi41NjZsMTAuMzk1IDQ0LjA3OWwxMS4zODEtNDQuMDc5Yy4zOTUtMS4zMTYuODU2LTIuMTcgMS4zMTYtMi41NjZjLjUyNi0uMzk0IDEuMzgyLS42NTcgMi41NjYtLjY1N2g2LjExOGMxLjA1MyAwIDEuNjQ1LjUyNiAxLjY0NSAxLjY0NGMwIC4zMy0uMDY2LjY1OC0uMTMyIDEuMDUzYy0uMDY1LjM5NS0uMTk3LjkyLS40NiAxLjY0NWwtMTUuODU1IDUwLjg1NXEtLjU5MyAxLjk3NC0xLjM4MiAyLjU2NmMtLjUyNi4zOTQtMS4zODIuNjU4LTIuNS42NThoLTUuNjU4Yy0xLjI1IDAtMi4xMDUtLjE5OC0yLjYzMS0uNjU4Yy0uNTI3LS40NjEtLjk4Ny0xLjMxNi0xLjI1LTIuNjMybC0xMC4xOTgtNDIuNDM0bC0xMC4xMzEgNDIuMzY4Yy0uMzI5IDEuMzE2LS43MjQgMi4xNzEtMS4yNSAyLjYzMmMtLjUyNy40Ni0xLjQ0OC42NTgtMi42MzIuNjU4em04NC41NCAxLjc3NmMtMy40MjEgMC02Ljg0Mi0uMzk1LTEwLjEzMi0xLjE4NGMtMy4yODktLjc5LTUuODU1LTEuNjQ1LTcuNTY2LTIuNjMyYy0xLjA1Mi0uNTkyLTEuNzc2LTEuMjUtMi4wMzktMS44NDJhNC42NSA0LjY1IDAgMCAxLS4zOTUtMS44NDJ2LTMuMzU1YzAtMS4zODIuNTI2LTIuMDQgMS41MTMtMi4wNHEuNTkzIDAgMS4xODQuMTk4Yy4zOTUuMTMxLjk4Ny4zOTQgMS42NDUuNjU4YTM1LjggMzUuOCAwIDAgMCA3LjIzNyAyLjMwMmEzOS41IDM5LjUgMCAwIDAgNy44MjkuNzljNC4xNDUgMCA3LjM2OC0uNzI0IDkuNjA1LTIuMTcxYzIuMjM3LTEuNDQ4IDMuNDIxLTMuNTUzIDMuNDIxLTYuMjVjMC0xLjg0Mi0uNTkyLTMuMzU2LTEuNzc2LTQuNjA2cy0zLjQyMS0yLjM2OC02LjY0NS0zLjQyMWwtOS41MzktMi45NmMtNC44MDMtMS41MTMtOC4zNTYtMy43NS0xMC41MjctNi43MWMtMi4xNzEtMi44OTUtMy4yODktNi4xMi0zLjI4OS05LjU0cTAtNC4xNDQgMS43NzYtNy4zMDNjMS4xODQtMi4xMDUgMi43NjMtMy45NDcgNC43MzctNS4zOTRjMS45NzQtMS41MTQgNC4yMTEtMi42MzIgNi44NDItMy40MjJjMi42MzItLjc5IDUuMzk1LTEuMTE4IDguMjktMS4xMThjMS40NDcgMCAyLjk2LjA2NiA0LjQwOC4yNjNjMS41MTMuMTk3IDIuODk0LjQ2IDQuMjc2LjcyNGMxLjMxNi4zMjkgMi41NjYuNjU4IDMuNzUgMS4wNTNxMS43NzYuNTkxIDIuNzYzIDEuMTg0Yy45MjEuNTI2IDEuNTc5IDEuMDUyIDEuOTc0IDEuNjQ0cS41OTIuNzkuNTkyIDIuMTcydjMuMDkyYzAgMS4zODEtLjUyNiAyLjEwNS0xLjUxMyAyLjEwNWMtLjUyNyAwLTEuMzgyLS4yNjMtMi41LS43OXEtNS42MjUtMi41NjUtMTIuNjMyLTIuNTY1Yy0zLjc1IDAtNi43MS41OTItOC43NSAxLjg0MnMtMy4wOTIgMy4xNTgtMy4wOTIgNS44NTVjMCAxLjg0Mi42NTggMy40MjEgMS45NzQgNC42NzFzMy43NSAyLjUgNy4yMzcgMy42MThsOS4zNDIgMi45NmM0LjczNiAxLjUxNCA4LjE1OCAzLjYxOSAxMC4xOTcgNi4zMTdzMy4wMjYgNS43ODkgMy4wMjYgOS4yMWMwIDIuODI5LS41OTIgNS4zOTUtMS43MSA3LjYzMmMtMS4xODQgMi4yMzctMi43NjMgNC4yMS00LjgwMyA1Ljc4OWMtMi4wMzkgMS42NDUtNC40NzQgMi44MjktNy4zMDIgMy42ODVjLTIuOTYxLjkyMS02LjA1MyAxLjM4MS05LjQwOCAxLjM4MSIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIwNy44MzcgMTYyLjgxNmMtMjEuNjQ1IDE1Ljk4Ny01My4wOTIgMjQuNDc0LTgwLjEzMiAyNC40NzRjLTM3Ljg5NCAwLTcyLjA0LTE0LjAxNC05Ny44MjktMzcuMzAzYy0yLjA0LTEuODQyLS4xOTctNC4zNDIgMi4yMzctMi44OTVjMjcuODk1IDE2LjE4NCA2Mi4zMDMgMjUuOTg3IDk3Ljg5NSAyNS45ODdjMjQuMDEzIDAgNTAuMzk1LTUgNzQuNjcxLTE1LjI2M2MzLjYxOC0xLjY0NSA2LjcxIDIuMzY4IDMuMTU4IDUiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIxNi44NSAxNTIuNTUzYy0yLjc2My0zLjU1My0xOC4yODktMS43MTEtMjUuMzI5LS44NTZjLTIuMTA1LjI2NC0yLjQzNC0xLjU3OS0uNTI2LTIuOTZjMTIuMzY4LTguNjg0IDMyLjY5Ny02LjE4NCAzNS4wNjYtMy4yOWMyLjM2OCAyLjk2MS0uNjU4IDIzLjI5LTEyLjIzNyAzMy4wMjdjLTEuNzc3IDEuNTEzLTMuNDg3LjcyMy0yLjY5OC0xLjI1YzIuNjMyLTYuNTEzIDguNDg3LTIxLjE4NSA1LjcyNC0yNC42NzEiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=", - resources: 102, - added: "Feb 11, 2024", - }, - { - id: 13, - account: "aws", - provider_id: 740350143856, - connected: true, - group: "Security", - scan_status: "active", - scan_status_time: "9 minutes, 50 seconds", - last_scan: "Jul 17, 2024 4:46am UTC", - next_scan: "Jul 18, 2024 4:46am UTC", - provider_img: - "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgdmlld0JveD0iMCAwIDI1NiAyNTYiPjxnIGZpbGw9Im5vbmUiPjxyZWN0IHdpZHRoPSIyNTYiIGhlaWdodD0iMjU2IiBmaWxsPSIjZjRmMmVkIiByeD0iNjAiLz48cGF0aCBmaWxsPSIjMjUyZjNlIiBkPSJNODQuNzQ1IDExMS45NjFjMCAyLjQzNC4yNjMgNC40MDcuNzIzIDUuODU1YTM1IDM1IDAgMCAwIDIuMTA2IDQuNzM3Yy4zMjkuNTI2LjQ2IDEuMDUyLjQ2IDEuNTEzYzAgLjY1OC0uMzk1IDEuMzE2LTEuMjUgMS45NzNsLTQuMTQ1IDIuNzY0Yy0uNTkyLjM5NC0xLjE4NC41OTItMS43MS41OTJjLS42NTggMC0xLjMxNi0uMzI5LTEuOTc0LS45MjFhMjAuNCAyMC40IDAgMCAxLTIuMzY4LTMuMDkyYTUxIDUxIDAgMCAxLTIuMDQtMy44ODJxLTcuNjk3IDkuMDgtMTkuMzQyIDkuMDc5Yy01LjUyNiAwLTkuOTM0LTEuNTc5LTEzLjE1OC00LjczN2MtMy4yMjMtMy4xNTgtNC44NjgtNy4zNjgtNC44NjgtMTIuNjMxYzAtNS41OTMgMS45NzQtMTAuMTMyIDUuOTg3LTEzLjU1M3M5LjM0Mi01LjEzMiAxNi4xMTgtNS4xMzJjMi4yMzcgMCA0LjU0LjE5OCA2Ljk3NC41MjdzNC45MzQuODU1IDcuNTY2IDEuNDQ3di00LjgwM2MwLTUtMS4wNTMtOC40ODctMy4wOTItMTAuNTI2Yy0yLjEwNi0yLjA0LTUuNjU4LTMuMDI2LTEwLjcyNC0zLjAyNmMtMi4zMDMgMC00LjY3MS4yNjMtNy4xMDUuODU1cy00LjgwMyAxLjMxNi03LjEwNiAyLjIzN2ExOSAxOSAwIDAgMS0yLjMwMi44NTVjLS40Ni4xMzItLjc5LjE5OC0xLjA1My4xOThjLS45MiAwLTEuMzgyLS42NTgtMS4zODItMi4wNHYtMy4yMjRjMC0xLjA1Mi4xMzItMS44NDIuNDYxLTIuMzAycy45MjEtLjkyMSAxLjg0Mi0xLjM4MnEzLjQ1NC0xLjc3NiA4LjI5LTIuOTZjMy4yMjMtLjg1NiA2LjY0NC0xLjI1IDEwLjI2My0xLjI1YzcuODI5IDAgMTMuNTUyIDEuNzc2IDE3LjIzNyA1LjMyOGMzLjYxOCAzLjU1MyA1LjQ2IDguOTQ4IDUuNDYgMTYuMTg1djIxLjMxNnptLTI2LjcxIDEwYzIuMTcgMCA0LjQwNy0uMzk1IDYuNzc2LTEuMTg1YzIuMzY4LS43ODkgNC40NzMtMi4yMzcgNi4yNS00LjIxYzEuMDUyLTEuMjUgMS44NDItMi42MzIgMi4yMzYtNC4yMTFzLjY1OC0zLjQ4Ny42NTgtNS43MjN2LTIuNzY0YTU1IDU1IDAgMCAwLTYuMDUyLTEuMTE4YTUwIDUwIDAgMCAwLTYuMTg1LS4zOTVjLTQuNDA4IDAtNy42MzEuODU2LTkuODAyIDIuNjMycy0zLjIyNCA0LjI3Ni0zLjIyNCA3LjU2NmMwIDMuMDkyLjc5IDUuMzk0IDIuNDM0IDYuOTczYzEuNTggMS42NDUgMy44ODIgMi40MzUgNi45MDggMi40MzVtNTIuODI4IDcuMTA1Yy0xLjE4NCAwLTEuOTc0LS4xOTgtMi41LS42NThjLS41MjYtLjM5NS0uOTg3LTEuMzE2LTEuMzgxLTIuNTY2bC0xNS40Ni01MC44NTVjLS4zOTYtMS4zMTYtLjU5My0yLjE3MS0uNTkzLTIuNjMyYzAtMS4wNTIuNTI2LTEuNjQ1IDEuNTc5LTEuNjQ1aDYuNDQ3YzEuMjUgMCAyLjEwNi4xOTggMi41NjYuNjU4Yy41MjYuMzk1LjkyMSAxLjMxNiAxLjMxNiAyLjU2NmwxMS4wNTIgNDMuNTUzbDEwLjI2NC00My41NTNjLjMyOS0xLjMxNi43MjMtMi4xNyAxLjI1LTIuNTY2Yy41MjYtLjM5NCAxLjQ0Ny0uNjU3IDIuNjMxLS42NTdoNS4yNjNjMS4yNSAwIDIuMTA2LjE5NyAyLjYzMi42NTdjLjUyNi4zOTUuOTg3IDEuMzE2IDEuMjUgMi41NjZsMTAuMzk1IDQ0LjA3OWwxMS4zODEtNDQuMDc5Yy4zOTUtMS4zMTYuODU2LTIuMTcgMS4zMTYtMi41NjZjLjUyNi0uMzk0IDEuMzgyLS42NTcgMi41NjYtLjY1N2g2LjExOGMxLjA1MyAwIDEuNjQ1LjUyNiAxLjY0NSAxLjY0NGMwIC4zMy0uMDY2LjY1OC0uMTMyIDEuMDUzYy0uMDY1LjM5NS0uMTk3LjkyLS40NiAxLjY0NWwtMTUuODU1IDUwLjg1NXEtLjU5MyAxLjk3NC0xLjM4MiAyLjU2NmMtLjUyNi4zOTQtMS4zODIuNjU4LTIuNS42NThoLTUuNjU4Yy0xLjI1IDAtMi4xMDUtLjE5OC0yLjYzMS0uNjU4Yy0uNTI3LS40NjEtLjk4Ny0xLjMxNi0xLjI1LTIuNjMybC0xMC4xOTgtNDIuNDM0bC0xMC4xMzEgNDIuMzY4Yy0uMzI5IDEuMzE2LS43MjQgMi4xNzEtMS4yNSAyLjYzMmMtLjUyNy40Ni0xLjQ0OC42NTgtMi42MzIuNjU4em04NC41NCAxLjc3NmMtMy40MjEgMC02Ljg0Mi0uMzk1LTEwLjEzMi0xLjE4NGMtMy4yODktLjc5LTUuODU1LTEuNjQ1LTcuNTY2LTIuNjMyYy0xLjA1Mi0uNTkyLTEuNzc2LTEuMjUtMi4wMzktMS44NDJhNC42NSA0LjY1IDAgMCAxLS4zOTUtMS44NDJ2LTMuMzU1YzAtMS4zODIuNTI2LTIuMDQgMS41MTMtMi4wNHEuNTkzIDAgMS4xODQuMTk4Yy4zOTUuMTMxLjk4Ny4zOTQgMS42NDUuNjU4YTM1LjggMzUuOCAwIDAgMCA3LjIzNyAyLjMwMmEzOS41IDM5LjUgMCAwIDAgNy44MjkuNzljNC4xNDUgMCA3LjM2OC0uNzI0IDkuNjA1LTIuMTcxYzIuMjM3LTEuNDQ4IDMuNDIxLTMuNTUzIDMuNDIxLTYuMjVjMC0xLjg0Mi0uNTkyLTMuMzU2LTEuNzc2LTQuNjA2cy0zLjQyMS0yLjM2OC02LjY0NS0zLjQyMWwtOS41MzktMi45NmMtNC44MDMtMS41MTMtOC4zNTYtMy43NS0xMC41MjctNi43MWMtMi4xNzEtMi44OTUtMy4yODktNi4xMi0zLjI4OS05LjU0cTAtNC4xNDQgMS43NzYtNy4zMDNjMS4xODQtMi4xMDUgMi43NjMtMy45NDcgNC43MzctNS4zOTRjMS45NzQtMS41MTQgNC4yMTEtMi42MzIgNi44NDItMy40MjJjMi42MzItLjc5IDUuMzk1LTEuMTE4IDguMjktMS4xMThjMS40NDcgMCAyLjk2LjA2NiA0LjQwOC4yNjNjMS41MTMuMTk3IDIuODk0LjQ2IDQuMjc2LjcyNGMxLjMxNi4zMjkgMi41NjYuNjU4IDMuNzUgMS4wNTNxMS43NzYuNTkxIDIuNzYzIDEuMTg0Yy45MjEuNTI2IDEuNTc5IDEuMDUyIDEuOTc0IDEuNjQ0cS41OTIuNzkuNTkyIDIuMTcydjMuMDkyYzAgMS4zODEtLjUyNiAyLjEwNS0xLjUxMyAyLjEwNWMtLjUyNyAwLTEuMzgyLS4yNjMtMi41LS43OXEtNS42MjUtMi41NjUtMTIuNjMyLTIuNTY1Yy0zLjc1IDAtNi43MS41OTItOC43NSAxLjg0MnMtMy4wOTIgMy4xNTgtMy4wOTIgNS44NTVjMCAxLjg0Mi42NTggMy40MjEgMS45NzQgNC42NzFzMy43NSAyLjUgNy4yMzcgMy42MThsOS4zNDIgMi45NmM0LjczNiAxLjUxNCA4LjE1OCAzLjYxOSAxMC4xOTcgNi4zMTdzMy4wMjYgNS43ODkgMy4wMjYgOS4yMWMwIDIuODI5LS41OTIgNS4zOTUtMS43MSA3LjYzMmMtMS4xODQgMi4yMzctMi43NjMgNC4yMS00LjgwMyA1Ljc4OWMtMi4wMzkgMS42NDUtNC40NzQgMi44MjktNy4zMDIgMy42ODVjLTIuOTYxLjkyMS02LjA1MyAxLjM4MS05LjQwOCAxLjM4MSIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIwNy44MzcgMTYyLjgxNmMtMjEuNjQ1IDE1Ljk4Ny01My4wOTIgMjQuNDc0LTgwLjEzMiAyNC40NzRjLTM3Ljg5NCAwLTcyLjA0LTE0LjAxNC05Ny44MjktMzcuMzAzYy0yLjA0LTEuODQyLS4xOTctNC4zNDIgMi4yMzctMi44OTVjMjcuODk1IDE2LjE4NCA2Mi4zMDMgMjUuOTg3IDk3Ljg5NSAyNS45ODdjMjQuMDEzIDAgNTAuMzk1LTUgNzQuNjcxLTE1LjI2M2MzLjYxOC0xLjY0NSA2LjcxIDIuMzY4IDMuMTU4IDUiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIxNi44NSAxNTIuNTUzYy0yLjc2My0zLjU1My0xOC4yODktMS43MTEtMjUuMzI5LS44NTZjLTIuMTA1LjI2NC0yLjQzNC0xLjU3OS0uNTI2LTIuOTZjMTIuMzY4LTguNjg0IDMyLjY5Ny02LjE4NCAzNS4wNjYtMy4yOWMyLjM2OCAyLjk2MS0uNjU4IDIzLjI5LTEyLjIzNyAzMy4wMjdjLTEuNzc3IDEuNTEzLTMuNDg3LjcyMy0yLjY5OC0xLjI1YzIuNjMyLTYuNTEzIDguNDg3LTIxLjE4NSA1LjcyNC0yNC42NzEiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=", - resources: 154, - added: "Jan 25, 2024", - }, - { - id: 14, - account: "aws", - provider_id: 740350143857, - connected: true, - group: "Marketing", - scan_status: "active", - scan_status_time: "3 minutes, 55 seconds", - last_scan: "Jul 17, 2024 3:46am UTC", - next_scan: "Jul 18, 2024 3:46am UTC", - provider_img: - "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgdmlld0JveD0iMCAwIDI1NiAyNTYiPjxnIGZpbGw9Im5vbmUiPjxyZWN0IHdpZHRoPSIyNTYiIGhlaWdodD0iMjU2IiBmaWxsPSIjZjRmMmVkIiByeD0iNjAiLz48cGF0aCBmaWxsPSIjMjUyZjNlIiBkPSJNODQuNzQ1IDExMS45NjFjMCAyLjQzNC4yNjMgNC40MDcuNzIzIDUuODU1YTM1IDM1IDAgMCAwIDIuMTA2IDQuNzM3Yy4zMjkuNTI2LjQ2IDEuMDUyLjQ2IDEuNTEzYzAgLjY1OC0uMzk1IDEuMzE2LTEuMjUgMS45NzNsLTQuMTQ1IDIuNzY0Yy0uNTkyLjM5NC0xLjE4NC41OTItMS43MS41OTJjLS42NTggMC0xLjMxNi0uMzI5LTEuOTc0LS45MjFhMjAuNCAyMC40IDAgMCAxLTIuMzY4LTMuMDkyYTUxIDUxIDAgMCAxLTIuMDQtMy44ODJxLTcuNjk3IDkuMDgtMTkuMzQyIDkuMDc5Yy01LjUyNiAwLTkuOTM0LTEuNTc5LTEzLjE1OC00LjczN2MtMy4yMjMtMy4xNTgtNC44NjgtNy4zNjgtNC44NjgtMTIuNjMxYzAtNS41OTMgMS45NzQtMTAuMTMyIDUuOTg3LTEzLjU1M3M5LjM0Mi01LjEzMiAxNi4xMTgtNS4xMzJjMi4yMzcgMCA0LjU0LjE5OCA2Ljk3NC41MjdzNC45MzQuODU1IDcuNTY2IDEuNDQ3di00LjgwM2MwLTUtMS4wNTMtOC40ODctMy4wOTItMTAuNTI2Yy0yLjEwNi0yLjA0LTUuNjU4LTMuMDI2LTEwLjcyNC0zLjAyNmMtMi4zMDMgMC00LjY3MS4yNjMtNy4xMDUuODU1cy00LjgwMyAxLjMxNi03LjEwNiAyLjIzN2ExOSAxOSAwIDAgMS0yLjMwMi44NTVjLS40Ni4xMzItLjc5LjE5OC0xLjA1My4xOThjLS45MiAwLTEuMzgyLS42NTgtMS4zODItMi4wNHYtMy4yMjRjMC0xLjA1Mi4xMzItMS44NDIuNDYxLTIuMzAycy45MjEtLjkyMSAxLjg0Mi0xLjM4MnEzLjQ1NC0xLjc3NiA4LjI5LTIuOTZjMy4yMjMtLjg1NiA2LjY0NC0xLjI1IDEwLjI2My0xLjI1YzcuODI5IDAgMTMuNTUyIDEuNzc2IDE3LjIzNyA1LjMyOGMzLjYxOCAzLjU1MyA1LjQ2IDguOTQ4IDUuNDYgMTYuMTg1djIxLjMxNnptLTI2LjcxIDEwYzIuMTcgMCA0LjQwNy0uMzk1IDYuNzc2LTEuMTg1YzIuMzY4LS43ODkgNC40NzMtMi4yMzcgNi4yNS00LjIxYzEuMDUyLTEuMjUgMS44NDItMi42MzIgMi4yMzYtNC4yMTFzLjY1OC0zLjQ4Ny42NTgtNS43MjN2LTIuNzY0YTU1IDU1IDAgMCAwLTYuMDUyLTEuMTE4YTUwIDUwIDAgMCAwLTYuMTg1LS4zOTVjLTQuNDA4IDAtNy42MzEuODU2LTkuODAyIDIuNjMycy0zLjIyNCA0LjI3Ni0zLjIyNCA3LjU2NmMwIDMuMDkyLjc5IDUuMzk0IDIuNDM0IDYuOTczYzEuNTggMS42NDUgMy44ODIgMi40MzUgNi45MDggMi40MzVtNTIuODI4IDcuMTA1Yy0xLjE4NCAwLTEuOTc0LS4xOTgtMi41LS42NThjLS41MjYtLjM5NS0uOTg3LTEuMzE2LTEuMzgxLTIuNTY2bC0xNS40Ni01MC44NTVjLS4zOTYtMS4zMTYtLjU5My0yLjE3MS0uNTkzLTIuNjMyYzAtMS4wNTIuNTI2LTEuNjQ1IDEuNTc5LTEuNjQ1aDYuNDQ3YzEuMjUgMCAyLjEwNi4xOTggMi41NjYuNjU4Yy41MjYuMzk1LjkyMSAxLjMxNiAxLjMxNiAyLjU2NmwxMS4wNTIgNDMuNTUzbDEwLjI2NC00My41NTNjLjMyOS0xLjMxNi43MjMtMi4xNyAxLjI1LTIuNTY2Yy41MjYtLjM5NCAxLjQ0Ny0uNjU3IDIuNjMxLS42NTdoNS4yNjNjMS4yNSAwIDIuMTA2LjE5NyAyLjYzMi42NTdjLjUyNi4zOTUuOTg3IDEuMzE2IDEuMjUgMi41NjZsMTAuMzk1IDQ0LjA3OWwxMS4zODEtNDQuMDc5Yy4zOTUtMS4zMTYuODU2LTIuMTcgMS4zMTYtMi41NjZjLjUyNi0uMzk0IDEuMzgyLS42NTcgMi41NjYtLjY1N2g2LjExOGMxLjA1MyAwIDEuNjQ1LjUyNiAxLjY0NSAxLjY0NGMwIC4zMy0uMDY2LjY1OC0uMTMyIDEuMDUzYy0uMDY1LjM5NS0uMTk3LjkyLS40NiAxLjY0NWwtMTUuODU1IDUwLjg1NXEtLjU5MyAxLjk3NC0xLjM4MiAyLjU2NmMtLjUyNi4zOTQtMS4zODIuNjU4LTIuNS42NThoLTUuNjU4Yy0xLjI1IDAtMi4xMDUtLjE5OC0yLjYzMS0uNjU4Yy0uNTI3LS40NjEtLjk4Ny0xLjMxNi0xLjI1LTIuNjMybC0xMC4xOTgtNDIuNDM0bC0xMC4xMzEgNDIuMzY4Yy0uMzI5IDEuMzE2LS43MjQgMi4xNzEtMS4yNSAyLjYzMmMtLjUyNy40Ni0xLjQ0OC42NTgtMi42MzIuNjU4em04NC41NCAxLjc3NmMtMy40MjEgMC02Ljg0Mi0uMzk1LTEwLjEzMi0xLjE4NGMtMy4yODktLjc5LTUuODU1LTEuNjQ1LTcuNTY2LTIuNjMyYy0xLjA1Mi0uNTkyLTEuNzc2LTEuMjUtMi4wMzktMS44NDJhNC42NSA0LjY1IDAgMCAxLS4zOTUtMS44NDJ2LTMuMzU1YzAtMS4zODIuNTI2LTIuMDQgMS41MTMtMi4wNHEuNTkzIDAgMS4xODQuMTk4Yy4zOTUuMTMxLjk4Ny4zOTQgMS42NDUuNjU4YTM1LjggMzUuOCAwIDAgMCA3LjIzNyAyLjMwMmEzOS41IDM5LjUgMCAwIDAgNy44MjkuNzljNC4xNDUgMCA3LjM2OC0uNzI0IDkuNjA1LTIuMTcxYzIuMjM3LTEuNDQ4IDMuNDIxLTMuNTUzIDMuNDIxLTYuMjVjMC0xLjg0Mi0uNTkyLTMuMzU2LTEuNzc2LTQuNjA2cy0zLjQyMS0yLjM2OC02LjY0NS0zLjQyMWwtOS41MzktMi45NmMtNC44MDMtMS41MTMtOC4zNTYtMy43NS0xMC41MjctNi43MWMtMi4xNzEtMi44OTUtMy4yODktNi4xMi0zLjI4OS05LjU0cTAtNC4xNDQgMS43NzYtNy4zMDNjMS4xODQtMi4xMDUgMi43NjMtMy45NDcgNC43MzctNS4zOTRjMS45NzQtMS41MTQgNC4yMTEtMi42MzIgNi44NDItMy40MjJjMi42MzItLjc5IDUuMzk1LTEuMTE4IDguMjktMS4xMThjMS40NDcgMCAyLjk2LjA2NiA0LjQwOC4yNjNjMS41MTMuMTk3IDIuODk0LjQ2IDQuMjc2LjcyNGMxLjMxNi4zMjkgMi41NjYuNjU4IDMuNzUgMS4wNTNxMS43NzYuNTkxIDIuNzYzIDEuMTg0Yy45MjEuNTI2IDEuNTc5IDEuMDUyIDEuOTc0IDEuNjQ0cS41OTIuNzkuNTkyIDIuMTcydjMuMDkyYzAgMS4zODEtLjUyNiAyLjEwNS0xLjUxMyAyLjEwNWMtLjUyNyAwLTEuMzgyLS4yNjMtMi41LS43OXEtNS42MjUtMi41NjUtMTIuNjMyLTIuNTY1Yy0zLjc1IDAtNi43MS41OTItOC43NSAxLjg0MnMtMy4wOTIgMy4xNTgtMy4wOTIgNS44NTVjMCAxLjg0Mi42NTggMy40MjEgMS45NzQgNC42NzFzMy43NSAyLjUgNy4yMzcgMy42MThsOS4zNDIgMi45NmM0LjczNiAxLjUxNCA4LjE1OCAzLjYxOSAxMC4xOTcgNi4zMTdzMy4wMjYgNS43ODkgMy4wMjYgOS4yMWMwIDIuODI5LS41OTIgNS4zOTUtMS43MSA3LjYzMmMtMS4xODQgMi4yMzctMi43NjMgNC4yMS00LjgwMyA1Ljc4OWMtMi4wMzkgMS42NDUtNC40NzQgMi44MjktNy4zMDIgMy42ODVjLTIuOTYxLjkyMS02LjA1MyAxLjM4MS05LjQwOCAxLjM4MSIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIwNy44MzcgMTYyLjgxNmMtMjEuNjQ1IDE1Ljk4Ny01My4wOTIgMjQuNDc0LTgwLjEzMiAyNC40NzRjLTM3Ljg5NCAwLTcyLjA0LTE0LjAxNC05Ny44MjktMzcuMzAzYy0yLjA0LTEuODQyLS4xOTctNC4zNDIgMi4yMzctMi44OTVjMjcuODk1IDE2LjE4NCA2Mi4zMDMgMjUuOTg3IDk3Ljg5NSAyNS45ODdjMjQuMDEzIDAgNTAuMzk1LTUgNzQuNjcxLTE1LjI2M2MzLjYxOC0xLjY0NSA2LjcxIDIuMzY4IDMuMTU4IDUiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIxNi44NSAxNTIuNTUzYy0yLjc2My0zLjU1My0xOC4yODktMS43MTEtMjUuMzI5LS44NTZjLTIuMTA1LjI2NC0yLjQzNC0xLjU3OS0uNTI2LTIuOTZjMTIuMzY4LTguNjg0IDMyLjY5Ny02LjE4NCAzNS4wNjYtMy4yOWMyLjM2OCAyLjk2MS0uNjU4IDIzLjI5LTEyLjIzNyAzMy4wMjdjLTEuNzc3IDEuNTEzLTMuNDg3LjcyMy0yLjY5OC0xLjI1YzIuNjMyLTYuNTEzIDguNDg3LTIxLjE4NSA1LjcyNC0yNC42NzEiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=", - resources: 138, - added: "Mar 12, 2024", - }, - { - id: 15, - account: "aws", - provider_id: 740350143858, - connected: false, - group: "Information Technology", - scan_status: "paused", - scan_status_time: "13 minutes, 25 seconds", - last_scan: "Jul 17, 2024 2:46am UTC", - next_scan: "Jul 18, 2024 2:46am UTC", - provider_img: - "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgdmlld0JveD0iMCAwIDI1NiAyNTYiPjxnIGZpbGw9Im5vbmUiPjxyZWN0IHdpZHRoPSIyNTYiIGhlaWdodD0iMjU2IiBmaWxsPSIjZjRmMmVkIiByeD0iNjAiLz48cGF0aCBmaWxsPSIjMjUyZjNlIiBkPSJNODQuNzQ1IDExMS45NjFjMCAyLjQzNC4yNjMgNC40MDcuNzIzIDUuODU1YTM1IDM1IDAgMCAwIDIuMTA2IDQuNzM3Yy4zMjkuNTI2LjQ2IDEuMDUyLjQ2IDEuNTEzYzAgLjY1OC0uMzk1IDEuMzE2LTEuMjUgMS45NzNsLTQuMTQ1IDIuNzY0Yy0uNTkyLjM5NC0xLjE4NC41OTItMS43MS41OTJjLS42NTggMC0xLjMxNi0uMzI5LTEuOTc0LS45MjFhMjAuNCAyMC40IDAgMCAxLTIuMzY4LTMuMDkyYTUxIDUxIDAgMCAxLTIuMDQtMy44ODJxLTcuNjk3IDkuMDgtMTkuMzQyIDkuMDc5Yy01LjUyNiAwLTkuOTM0LTEuNTc5LTEzLjE1OC00LjczN2MtMy4yMjMtMy4xNTgtNC44NjgtNy4zNjgtNC44NjgtMTIuNjMxYzAtNS41OTMgMS45NzQtMTAuMTMyIDUuOTg3LTEzLjU1M3M5LjM0Mi01LjEzMiAxNi4xMTgtNS4xMzJjMi4yMzcgMCA0LjU0LjE5OCA2Ljk3NC41MjdzNC45MzQuODU1IDcuNTY2IDEuNDQ3di00LjgwM2MwLTUtMS4wNTMtOC40ODctMy4wOTItMTAuNTI2Yy0yLjEwNi0yLjA0LTUuNjU4LTMuMDI2LTEwLjcyNC0zLjAyNmMtMi4zMDMgMC00LjY3MS4yNjMtNy4xMDUuODU1cy00LjgwMyAxLjMxNi03LjEwNiAyLjIzN2ExOSAxOSAwIDAgMS0yLjMwMi44NTVjLS40Ni4xMzItLjc5LjE5OC0xLjA1My4xOThjLS45MiAwLTEuMzgyLS42NTgtMS4zODItMi4wNHYtMy4yMjRjMC0xLjA1Mi4xMzItMS44NDIuNDYxLTIuMzAycy45MjEtLjkyMSAxLjg0Mi0xLjM4MnEzLjQ1NC0xLjc3NiA4LjI5LTIuOTZjMy4yMjMtLjg1NiA2LjY0NC0xLjI1IDEwLjI2My0xLjI1YzcuODI5IDAgMTMuNTUyIDEuNzc2IDE3LjIzNyA1LjMyOGMzLjYxOCAzLjU1MyA1LjQ2IDguOTQ4IDUuNDYgMTYuMTg1djIxLjMxNnptLTI2LjcxIDEwYzIuMTcgMCA0LjQwNy0uMzk1IDYuNzc2LTEuMTg1YzIuMzY4LS43ODkgNC40NzMtMi4yMzcgNi4yNS00LjIxYzEuMDUyLTEuMjUgMS44NDItMi42MzIgMi4yMzYtNC4yMTFzLjY1OC0zLjQ4Ny42NTgtNS43MjN2LTIuNzY0YTU1IDU1IDAgMCAwLTYuMDUyLTEuMTE4YTUwIDUwIDAgMCAwLTYuMTg1LS4zOTVjLTQuNDA4IDAtNy42MzEuODU2LTkuODAyIDIuNjMycy0zLjIyNCA0LjI3Ni0zLjIyNCA3LjU2NmMwIDMuMDkyLjc5IDUuMzk0IDIuNDM0IDYuOTczYzEuNTggMS42NDUgMy44ODIgMi40MzUgNi45MDggMi40MzVtNTIuODI4IDcuMTA1Yy0xLjE4NCAwLTEuOTc0LS4xOTgtMi41LS42NThjLS41MjYtLjM5NS0uOTg3LTEuMzE2LTEuMzgxLTIuNTY2bC0xNS40Ni01MC44NTVjLS4zOTYtMS4zMTYtLjU5My0yLjE3MS0uNTkzLTIuNjMyYzAtMS4wNTIuNTI2LTEuNjQ1IDEuNTc5LTEuNjQ1aDYuNDQ3YzEuMjUgMCAyLjEwNi4xOTggMi41NjYuNjU4Yy41MjYuMzk1LjkyMSAxLjMxNiAxLjMxNiAyLjU2NmwxMS4wNTIgNDMuNTUzbDEwLjI2NC00My41NTNjLjMyOS0xLjMxNi43MjMtMi4xNyAxLjI1LTIuNTY2Yy41MjYtLjM5NCAxLjQ0Ny0uNjU3IDIuNjMxLS42NTdoNS4yNjNjMS4yNSAwIDIuMTA2LjE5NyAyLjYzMi42NTdjLjUyNi4zOTUuOTg3IDEuMzE2IDEuMjUgMi41NjZsMTAuMzk1IDQ0LjA3OWwxMS4zODEtNDQuMDc5Yy4zOTUtMS4zMTYuODU2LTIuMTcgMS4zMTYtMi41NjZjLjUyNi0uMzk0IDEuMzgyLS42NTcgMi41NjYtLjY1N2g2LjExOGMxLjA1MyAwIDEuNjQ1LjUyNiAxLjY0NSAxLjY0NGMwIC4zMy0uMDY2LjY1OC0uMTMyIDEuMDUzYy0uMDY1LjM5NS0uMTk3LjkyLS40NiAxLjY0NWwtMTUuODU1IDUwLjg1NXEtLjU5MyAxLjk3NC0xLjM4MiAyLjU2NmMtLjUyNi4zOTQtMS4zODIuNjU4LTIuNS42NThoLTUuNjU4Yy0xLjI1IDAtMi4xMDUtLjE5OC0yLjYzMS0uNjU4Yy0uNTI3LS40NjEtLjk4Ny0xLjMxNi0xLjI1LTIuNjMybC0xMC4xOTgtNDIuNDM0bC0xMC4xMzEgNDIuMzY4Yy0uMzI5IDEuMzE2LS43MjQgMi4xNzEtMS4yNSAyLjYzMmMtLjUyNy40Ni0xLjQ0OC42NTgtMi42MzIuNjU4em04NC41NCAxLjc3NmMtMy40MjEgMC02Ljg0Mi0uMzk1LTEwLjEzMi0xLjE4NGMtMy4yODktLjc5LTUuODU1LTEuNjQ1LTcuNTY2LTIuNjMyYy0xLjA1Mi0uNTkyLTEuNzc2LTEuMjUtMi4wMzktMS44NDJhNC42NSA0LjY1IDAgMCAxLS4zOTUtMS44NDJ2LTMuMzU1YzAtMS4zODIuNTI2LTIuMDQgMS41MTMtMi4wNHEuNTkzIDAgMS4xODQuMTk4Yy4zOTUuMTMxLjk4Ny4zOTQgMS42NDUuNjU4YTM1LjggMzUuOCAwIDAgMCA3LjIzNyAyLjMwMmEzOS41IDM5LjUgMCAwIDAgNy44MjkuNzljNC4xNDUgMCA3LjM2OC0uNzI0IDkuNjA1LTIuMTcxYzIuMjM3LTEuNDQ4IDMuNDIxLTMuNTUzIDMuNDIxLTYuMjVjMC0xLjg0Mi0uNTkyLTMuMzU2LTEuNzc2LTQuNjA2cy0zLjQyMS0yLjM2OC02LjY0NS0zLjQyMWwtOS41MzktMi45NmMtNC44MDMtMS41MTMtOC4zNTYtMy43NS0xMC41MjctNi43MWMtMi4xNzEtMi44OTUtMy4yODktNi4xMi0zLjI4OS05LjU0cTAtNC4xNDQgMS43NzYtNy4zMDNjMS4xODQtMi4xMDUgMi43NjMtMy45NDcgNC43MzctNS4zOTRjMS45NzQtMS41MTQgNC4yMTEtMi42MzIgNi44NDItMy40MjJjMi42MzItLjc5IDUuMzk1LTEuMTE4IDguMjktMS4xMThjMS40NDcgMCAyLjk2LjA2NiA0LjQwOC4yNjNjMS41MTMuMTk3IDIuODk0LjQ2IDQuMjc2LjcyNGMxLjMxNi4zMjkgMi41NjYuNjU4IDMuNzUgMS4wNTNxMS43NzYuNTkxIDIuNzYzIDEuMTg0Yy45MjEuNTI2IDEuNTc5IDEuMDUyIDEuOTc0IDEuNjQ0cS41OTIuNzkuNTkyIDIuMTcydjMuMDkyYzAgMS4zODEtLjUyNiAyLjEwNS0xLjUxMyAyLjEwNWMtLjUyNyAwLTEuMzgyLS4yNjMtMi41LS43OXEtNS42MjUtMi41NjUtMTIuNjMyLTIuNTY1Yy0zLjc1IDAtNi43MS41OTItOC43NSAxLjg0MnMtMy4wOTIgMy4xNTgtMy4wOTIgNS44NTVjMCAxLjg0Mi42NTggMy40MjEgMS45NzQgNC42NzFzMy43NSAyLjUgNy4yMzcgMy42MThsOS4zNDIgMi45NmM0LjczNiAxLjUxNCA4LjE1OCAzLjYxOSAxMC4xOTcgNi4zMTdzMy4wMjYgNS43ODkgMy4wMjYgOS4yMWMwIDIuODI5LS41OTIgNS4zOTUtMS43MSA3LjYzMmMtMS4xODQgMi4yMzctMi43NjMgNC4yMS00LjgwMyA1Ljc4OWMtMi4wMzkgMS42NDUtNC40NzQgMi44MjktNy4zMDIgMy42ODVjLTIuOTYxLjkyMS02LjA1MyAxLjM4MS05LjQwOCAxLjM4MSIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIwNy44MzcgMTYyLjgxNmMtMjEuNjQ1IDE1Ljk4Ny01My4wOTIgMjQuNDc0LTgwLjEzMiAyNC40NzRjLTM3Ljg5NCAwLTcyLjA0LTE0LjAxNC05Ny44MjktMzcuMzAzYy0yLjA0LTEuODQyLS4xOTctNC4zNDIgMi4yMzctMi44OTVjMjcuODk1IDE2LjE4NCA2Mi4zMDMgMjUuOTg3IDk3Ljg5NSAyNS45ODdjMjQuMDEzIDAgNTAuMzk1LTUgNzQuNjcxLTE1LjI2M2MzLjYxOC0xLjY0NSA2LjcxIDIuMzY4IDMuMTU4IDUiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIxNi44NSAxNTIuNTUzYy0yLjc2My0zLjU1My0xOC4yODktMS43MTEtMjUuMzI5LS44NTZjLTIuMTA1LjI2NC0yLjQzNC0xLjU3OS0uNTI2LTIuOTZjMTIuMzY4LTguNjg0IDMyLjY5Ny02LjE4NCAzNS4wNjYtMy4yOWMyLjM2OCAyLjk2MS0uNjU4IDIzLjI5LTEyLjIzNyAzMy4wMjdjLTEuNzc3IDEuNTEzLTMuNDg3LjcyMy0yLjY5OC0xLjI1YzIuNjMyLTYuNTEzIDguNDg3LTIxLjE4NSA1LjcyNC0yNC42NzEiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=", - resources: 112, - added: "Feb 17, 2024", - }, - { - id: 16, - account: "aws", - provider_id: 740350143859, - connected: true, - group: "Sales", - scan_status: "active", - scan_status_time: "6 minutes, 10 seconds", - last_scan: "Jul 17, 2024 1:46am UTC", - next_scan: "Jul 18, 2024 1:46am UTC", - provider_img: - "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgdmlld0JveD0iMCAwIDI1NiAyNTYiPjxnIGZpbGw9Im5vbmUiPjxyZWN0IHdpZHRoPSIyNTYiIGhlaWdodD0iMjU2IiBmaWxsPSIjZjRmMmVkIiByeD0iNjAiLz48cGF0aCBmaWxsPSIjMjUyZjNlIiBkPSJNODQuNzQ1IDExMS45NjFjMCAyLjQzNC4yNjMgNC40MDcuNzIzIDUuODU1YTM1IDM1IDAgMCAwIDIuMTA2IDQuNzM3Yy4zMjkuNTI2LjQ2IDEuMDUyLjQ2IDEuNTEzYzAgLjY1OC0uMzk1IDEuMzE2LTEuMjUgMS45NzNsLTQuMTQ1IDIuNzY0Yy0uNTkyLjM5NC0xLjE4NC41OTItMS43MS41OTJjLS42NTggMC0xLjMxNi0uMzI5LTEuOTc0LS45MjFhMjAuNCAyMC40IDAgMCAxLTIuMzY4LTMuMDkyYTUxIDUxIDAgMCAxLTIuMDQtMy44ODJxLTcuNjk3IDkuMDgtMTkuMzQyIDkuMDc5Yy01LjUyNiAwLTkuOTM0LTEuNTc5LTEzLjE1OC00LjczN2MtMy4yMjMtMy4xNTgtNC44NjgtNy4zNjgtNC44NjgtMTIuNjMxYzAtNS41OTMgMS45NzQtMTAuMTMyIDUuOTg3LTEzLjU1M3M5LjM0Mi01LjEzMiAxNi4xMTgtNS4xMzJjMi4yMzcgMCA0LjU0LjE5OCA2Ljk3NC41MjdzNC45MzQuODU1IDcuNTY2IDEuNDQ3di00LjgwM2MwLTUtMS4wNTMtOC40ODctMy4wOTItMTAuNTI2Yy0yLjEwNi0yLjA0LTUuNjU4LTMuMDI2LTEwLjcyNC0zLjAyNmMtMi4zMDMgMC00LjY3MS4yNjMtNy4xMDUuODU1cy00LjgwMyAxLjMxNi03LjEwNiAyLjIzN2ExOSAxOSAwIDAgMS0yLjMwMi44NTVjLS40Ni4xMzItLjc5LjE5OC0xLjA1My4xOThjLS45MiAwLTEuMzgyLS42NTgtMS4zODItMi4wNHYtMy4yMjRjMC0xLjA1Mi4xMzItMS44NDIuNDYxLTIuMzAycy45MjEtLjkyMSAxLjg0Mi0xLjM4MnEzLjQ1NC0xLjc3NiA4LjI5LTIuOTZjMy4yMjMtLjg1NiA2LjY0NC0xLjI1IDEwLjI2My0xLjI1YzcuODI5IDAgMTMuNTUyIDEuNzc2IDE3LjIzNyA1LjMyOGMzLjYxOCAzLjU1MyA1LjQ2IDguOTQ4IDUuNDYgMTYuMTg1djIxLjMxNnptLTI2LjcxIDEwYzIuMTcgMCA0LjQwNy0uMzk1IDYuNzc2LTEuMTg1YzIuMzY4LS43ODkgNC40NzMtMi4yMzcgNi4yNS00LjIxYzEuMDUyLTEuMjUgMS44NDItMi42MzIgMi4yMzYtNC4yMTFzLjY1OC0zLjQ4Ny42NTgtNS43MjN2LTIuNzY0YTU1IDU1IDAgMCAwLTYuMDUyLTEuMTE4YTUwIDUwIDAgMCAwLTYuMTg1LS4zOTVjLTQuNDA4IDAtNy42MzEuODU2LTkuODAyIDIuNjMycy0zLjIyNCA0LjI3Ni0zLjIyNCA3LjU2NmMwIDMuMDkyLjc5IDUuMzk0IDIuNDM0IDYuOTczYzEuNTggMS42NDUgMy44ODIgMi40MzUgNi45MDggMi40MzVtNTIuODI4IDcuMTA1Yy0xLjE4NCAwLTEuOTc0LS4xOTgtMi41LS42NThjLS41MjYtLjM5NS0uOTg3LTEuMzE2LTEuMzgxLTIuNTY2bC0xNS40Ni01MC44NTVjLS4zOTYtMS4zMTYtLjU5My0yLjE3MS0uNTkzLTIuNjMyYzAtMS4wNTIuNTI2LTEuNjQ1IDEuNTc5LTEuNjQ1aDYuNDQ3YzEuMjUgMCAyLjEwNi4xOTggMi41NjYuNjU4Yy41MjYuMzk1LjkyMSAxLjMxNiAxLjMxNiAyLjU2NmwxMS4wNTIgNDMuNTUzbDEwLjI2NC00My41NTNjLjMyOS0xLjMxNi43MjMtMi4xNyAxLjI1LTIuNTY2Yy41MjYtLjM5NCAxLjQ0Ny0uNjU3IDIuNjMxLS42NTdoNS4yNjNjMS4yNSAwIDIuMTA2LjE5NyAyLjYzMi42NTdjLjUyNi4zOTUuOTg3IDEuMzE2IDEuMjUgMi41NjZsMTAuMzk1IDQ0LjA3OWwxMS4zODEtNDQuMDc5Yy4zOTUtMS4zMTYuODU2LTIuMTcgMS4zMTYtMi41NjZjLjUyNi0uMzk0IDEuMzgyLS42NTcgMi41NjYtLjY1N2g2LjExOGMxLjA1MyAwIDEuNjQ1LjUyNiAxLjY0NSAxLjY0NGMwIC4zMy0uMDY2LjY1OC0uMTMyIDEuMDUzYy0uMDY1LjM5NS0uMTk3LjkyLS40NiAxLjY0NWwtMTUuODU1IDUwLjg1NXEtLjU5MyAxLjk3NC0xLjM4MiAyLjU2NmMtLjUyNi4zOTQtMS4zODIuNjU4LTIuNS42NThoLTUuNjU4Yy0xLjI1IDAtMi4xMDUtLjE5OC0yLjYzMS0uNjU4Yy0uNTI3LS40NjEtLjk4Ny0xLjMxNi0xLjI1LTIuNjMybC0xMC4xOTgtNDIuNDM0bC0xMC4xMzEgNDIuMzY4Yy0uMzI5IDEuMzE2LS43MjQgMi4xNzEtMS4yNSAyLjYzMmMtLjUyNy40Ni0xLjQ0OC42NTgtMi42MzIuNjU4em04NC41NCAxLjc3NmMtMy40MjEgMC02Ljg0Mi0uMzk1LTEwLjEzMi0xLjE4NGMtMy4yODktLjc5LTUuODU1LTEuNjQ1LTcuNTY2LTIuNjMyYy0xLjA1Mi0uNTkyLTEuNzc2LTEuMjUtMi4wMzktMS44NDJhNC42NSA0LjY1IDAgMCAxLS4zOTUtMS44NDJ2LTMuMzU1YzAtMS4zODIuNTI2LTIuMDQgMS41MTMtMi4wNHEuNTkzIDAgMS4xODQuMTk4Yy4zOTUuMTMxLjk4Ny4zOTQgMS42NDUuNjU4YTM1LjggMzUuOCAwIDAgMCA3LjIzNyAyLjMwMmEzOS41IDM5LjUgMCAwIDAgNy44MjkuNzljNC4xNDUgMCA3LjM2OC0uNzI0IDkuNjA1LTIuMTcxYzIuMjM3LTEuNDQ4IDMuNDIxLTMuNTUzIDMuNDIxLTYuMjVjMC0xLjg0Mi0uNTkyLTMuMzU2LTEuNzc2LTQuNjA2cy0zLjQyMS0yLjM2OC02LjY0NS0zLjQyMWwtOS41MzktMi45NmMtNC44MDMtMS41MTMtOC4zNTYtMy43NS0xMC41MjctNi43MWMtMi4xNzEtMi44OTUtMy4yODktNi4xMi0zLjI4OS05LjU0cTAtNC4xNDQgMS43NzYtNy4zMDNjMS4xODQtMi4xMDUgMi43NjMtMy45NDcgNC43MzctNS4zOTRjMS45NzQtMS41MTQgNC4yMTEtMi42MzIgNi44NDItMy40MjJjMi42MzItLjc5IDUuMzk1LTEuMTE4IDguMjktMS4xMThjMS40NDcgMCAyLjk2LjA2NiA0LjQwOC4yNjNjMS41MTMuMTk3IDIuODk0LjQ2IDQuMjc2LjcyNGMxLjMxNi4zMjkgMi41NjYuNjU4IDMuNzUgMS4wNTNxMS43NzYuNTkxIDIuNzYzIDEuMTg0Yy45MjEuNTI2IDEuNTc5IDEuMDUyIDEuOTc0IDEuNjQ0cS41OTIuNzkuNTkyIDIuMTcydjMuMDkyYzAgMS4zODEtLjUyNiAyLjEwNS0xLjUxMyAyLjEwNWMtLjUyNyAwLTEuMzgyLS4yNjMtMi41LS43OXEtNS42MjUtMi41NjUtMTIuNjMyLTIuNTY1Yy0zLjc1IDAtNi43MS41OTItOC43NSAxLjg0MnMtMy4wOTIgMy4xNTgtMy4wOTIgNS44NTVjMCAxLjg0Mi42NTggMy40MjEgMS45NzQgNC42NzFzMy43NSAyLjUgNy4yMzcgMy42MThsOS4zNDIgMi45NmM0LjczNiAxLjUxNCA4LjE1OCAzLjYxOSAxMC4xOTcgNi4zMTdzMy4wMjYgNS43ODkgMy4wMjYgOS4yMWMwIDIuODI5LS41OTIgNS4zOTUtMS43MSA3LjYzMmMtMS4xODQgMi4yMzctMi43NjMgNC4yMS00LjgwMyA1Ljc4OWMtMi4wMzkgMS42NDUtNC40NzQgMi44MjktNy4zMDIgMy42ODVjLTIuOTYxLjkyMS02LjA1MyAxLjM4MS05LjQwOCAxLjM4MSIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIwNy44MzcgMTYyLjgxNmMtMjEuNjQ1IDE1Ljk4Ny01My4wOTIgMjQuNDc0LTgwLjEzMiAyNC40NzRjLTM3Ljg5NCAwLTcyLjA0LTE0LjAxNC05Ny44MjktMzcuMzAzYy0yLjA0LTEuODQyLS4xOTctNC4zNDIgMi4yMzctMi44OTVjMjcuODk1IDE2LjE4NCA2Mi4zMDMgMjUuOTg3IDk3Ljg5NSAyNS45ODdjMjQuMDEzIDAgNTAuMzk1LTUgNzQuNjcxLTE1LjI2M2MzLjYxOC0xLjY0NSA2LjcxIDIuMzY4IDMuMTU4IDUiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIxNi44NSAxNTIuNTUzYy0yLjc2My0zLjU1My0xOC4yODktMS43MTEtMjUuMzI5LS44NTZjLTIuMTA1LjI2NC0yLjQzNC0xLjU3OS0uNTI2LTIuOTZjMTIuMzY4LTguNjg0IDMyLjY5Ny02LjE4NCAzNS4wNjYtMy4yOWMyLjM2OCAyLjk2MS0uNjU4IDIzLjI5LTEyLjIzNyAzMy4wMjdjLTEuNzc3IDEuNTEzLTMuNDg3LjcyMy0yLjY5OC0xLjI1YzIuNjMyLTYuNTEzIDguNDg3LTIxLjE4NSA1LjcyNC0yNC42NzEiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=", - resources: 140, - added: "Apr 1, 2024", - }, - { - id: 17, - account: "aws", - provider_id: 740350143860, - connected: true, - group: "Analysis", - scan_status: "active", - scan_status_time: "5 minutes, 30 seconds", - last_scan: "Jul 17, 2024 12:46am UTC", - next_scan: "Jul 18, 2024 12:46am UTC", - provider_img: - "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgdmlld0JveD0iMCAwIDI1NiAyNTYiPjxnIGZpbGw9Im5vbmUiPjxyZWN0IHdpZHRoPSIyNTYiIGhlaWdodD0iMjU2IiBmaWxsPSIjZjRmMmVkIiByeD0iNjAiLz48cGF0aCBmaWxsPSIjMjUyZjNlIiBkPSJNODQuNzQ1IDExMS45NjFjMCAyLjQzNC4yNjMgNC40MDcuNzIzIDUuODU1YTM1IDM1IDAgMCAwIDIuMTA2IDQuNzM3Yy4zMjkuNTI2LjQ2IDEuMDUyLjQ2IDEuNTEzYzAgLjY1OC0uMzk1IDEuMzE2LTEuMjUgMS45NzNsLTQuMTQ1IDIuNzY0Yy0uNTkyLjM5NC0xLjE4NC41OTItMS43MS41OTJjLS42NTggMC0xLjMxNi0uMzI5LTEuOTc0LS45MjFhMjAuNCAyMC40IDAgMCAxLTIuMzY4LTMuMDkyYTUxIDUxIDAgMCAxLTIuMDQtMy44ODJxLTcuNjk3IDkuMDgtMTkuMzQyIDkuMDc5Yy01LjUyNiAwLTkuOTM0LTEuNTc5LTEzLjE1OC00LjczN2MtMy4yMjMtMy4xNTgtNC44NjgtNy4zNjgtNC44NjgtMTIuNjMxYzAtNS41OTMgMS45NzQtMTAuMTMyIDUuOTg3LTEzLjU1M3M5LjM0Mi01LjEzMiAxNi4xMTgtNS4xMzJjMi4yMzcgMCA0LjU0LjE5OCA2Ljk3NC41MjdzNC45MzQuODU1IDcuNTY2IDEuNDQ3di00LjgwM2MwLTUtMS4wNTMtOC40ODctMy4wOTItMTAuNTI2Yy0yLjEwNi0yLjA0LTUuNjU4LTMuMDI2LTEwLjcyNC0zLjAyNmMtMi4zMDMgMC00LjY3MS4yNjMtNy4xMDUuODU1cy00LjgwMyAxLjMxNi03LjEwNiAyLjIzN2ExOSAxOSAwIDAgMS0yLjMwMi44NTVjLS40Ni4xMzItLjc5LjE5OC0xLjA1My4xOThjLS45MiAwLTEuMzgyLS42NTgtMS4zODItMi4wNHYtMy4yMjRjMC0xLjA1Mi4xMzItMS44NDIuNDYxLTIuMzAycy45MjEtLjkyMSAxLjg0Mi0xLjM4MnEzLjQ1NC0xLjc3NiA4LjI5LTIuOTZjMy4yMjMtLjg1NiA2LjY0NC0xLjI1IDEwLjI2My0xLjI1YzcuODI5IDAgMTMuNTUyIDEuNzc2IDE3LjIzNyA1LjMyOGMzLjYxOCAzLjU1MyA1LjQ2IDguOTQ4IDUuNDYgMTYuMTg1djIxLjMxNnptLTI2LjcxIDEwYzIuMTcgMCA0LjQwNy0uMzk1IDYuNzc2LTEuMTg1YzIuMzY4LS43ODkgNC40NzMtMi4yMzcgNi4yNS00LjIxYzEuMDUyLTEuMjUgMS44NDItMi42MzIgMi4yMzYtNC4yMTFzLjY1OC0zLjQ4Ny42NTgtNS43MjN2LTIuNzY0YTU1IDU1IDAgMCAwLTYuMDUyLTEuMTE4YTUwIDUwIDAgMCAwLTYuMTg1LS4zOTVjLTQuNDA4IDAtNy42MzEuODU2LTkuODAyIDIuNjMycy0zLjIyNCA0LjI3Ni0zLjIyNCA3LjU2NmMwIDMuMDkyLjc5IDUuMzk0IDIuNDM0IDYuOTczYzEuNTggMS42NDUgMy44ODIgMi40MzUgNi45MDggMi40MzVtNTIuODI4IDcuMTA1Yy0xLjE4NCAwLTEuOTc0LS4xOTgtMi41LS42NThjLS41MjYtLjM5NS0uOTg3LTEuMzE2LTEuMzgxLTIuNTY2bC0xNS40Ni01MC44NTVjLS4zOTYtMS4zMTYtLjU5My0yLjE3MS0uNTkzLTIuNjMyYzAtMS4wNTIuNTI2LTEuNjQ1IDEuNTc5LTEuNjQ1aDYuNDQ3YzEuMjUgMCAyLjEwNi4xOTggMi41NjYuNjU4Yy41MjYuMzk1LjkyMSAxLjMxNiAxLjMxNiAyLjU2NmwxMS4wNTIgNDMuNTUzbDEwLjI2NC00My41NTNjLjMyOS0xLjMxNi43MjMtMi4xNyAxLjI1LTIuNTY2Yy41MjYtLjM5NCAxLjQ0Ny0uNjU3IDIuNjMxLS42NTdoNS4yNjNjMS4yNSAwIDIuMTA2LjE5NyAyLjYzMi42NTdjLjUyNi4zOTUuOTg3IDEuMzE2IDEuMjUgMi41NjZsMTAuMzk1IDQ0LjA3OWwxMS4zODEtNDQuMDc5Yy4zOTUtMS4zMTYuODU2LTIuMTcgMS4zMTYtMi41NjZjLjUyNi0uMzk0IDEuMzgyLS42NTcgMi41NjYtLjY1N2g2LjExOGMxLjA1MyAwIDEuNjQ1LjUyNiAxLjY0NSAxLjY0NGMwIC4zMy0uMDY2LjY1OC0uMTMyIDEuMDUzYy0uMDY1LjM5NS0uMTk3LjkyLS40NiAxLjY0NWwtMTUuODU1IDUwLjg1NXEtLjU5MyAxLjk3NC0xLjM4MiAyLjU2NmMtLjUyNi4zOTQtMS4zODIuNjU4LTIuNS42NThoLTUuNjU4Yy0xLjI1IDAtMi4xMDUtLjE5OC0yLjYzMS0uNjU4Yy0uNTI3LS40NjEtLjk4Ny0xLjMxNi0xLjI1LTIuNjMybC0xMC4xOTgtNDIuNDM0bC0xMC4xMzEgNDIuMzY4Yy0uMzI5IDEuMzE2LS43MjQgMi4xNzEtMS4yNSAyLjYzMmMtLjUyNy40Ni0xLjQ0OC42NTgtMi42MzIuNjU4em04NC41NCAxLjc3NmMtMy40MjEgMC02Ljg0Mi0uMzk1LTEwLjEzMi0xLjE4NGMtMy4yODktLjc5LTUuODU1LTEuNjQ1LTcuNTY2LTIuNjMyYy0xLjA1Mi0uNTkyLTEuNzc2LTEuMjUtMi4wMzktMS44NDJhNC42NSA0LjY1IDAgMCAxLS4zOTUtMS44NDJ2LTMuMzU1YzAtMS4zODIuNTI2LTIuMDQgMS41MTMtMi4wNHEuNTkzIDAgMS4xODQuMTk4Yy4zOTUuMTMxLjk4Ny4zOTQgMS42NDUuNjU4YTM1LjggMzUuOCAwIDAgMCA3LjIzNyAyLjMwMmEzOS41IDM5LjUgMCAwIDAgNy44MjkuNzljNC4xNDUgMCA3LjM2OC0uNzI0IDkuNjA1LTIuMTcxYzIuMjM3LTEuNDQ4IDMuNDIxLTMuNTUzIDMuNDIxLTYuMjVjMC0xLjg0Mi0uNTkyLTMuMzU2LTEuNzc2LTQuNjA2cy0zLjQyMS0yLjM2OC02LjY0NS0zLjQyMWwtOS41MzktMi45NmMtNC44MDMtMS41MTMtOC4zNTYtMy43NS0xMC41MjctNi43MWMtMi4xNzEtMi44OTUtMy4yODktNi4xMi0zLjI4OS05LjU0cTAtNC4xNDQgMS43NzYtNy4zMDNjMS4xODQtMi4xMDUgMi43NjMtMy45NDcgNC43MzctNS4zOTRjMS45NzQtMS41MTQgNC4yMTEtMi42MzIgNi44NDItMy40MjJjMi42MzItLjc5IDUuMzk1LTEuMTE4IDguMjktMS4xMThjMS40NDcgMCAyLjk2LjA2NiA0LjQwOC4yNjNjMS41MTMuMTk3IDIuODk0LjQ2IDQuMjc2LjcyNGMxLjMxNi4zMjkgMi41NjYuNjU4IDMuNzUgMS4wNTNxMS43NzYuNTkxIDIuNzYzIDEuMTg0Yy45MjEuNTI2IDEuNTc5IDEuMDUyIDEuOTc0IDEuNjQ0cS41OTIuNzkuNTkyIDIuMTcydjMuMDkyYzAgMS4zODEtLjUyNiAyLjEwNS0xLjUxMyAyLjEwNWMtLjUyNyAwLTEuMzgyLS4yNjMtMi41LS43OXEtNS42MjUtMi41NjUtMTIuNjMyLTIuNTY1Yy0zLjc1IDAtNi43MS41OTItOC43NSAxLjg0MnMtMy4wOTIgMy4xNTgtMy4wOTIgNS44NTVjMCAxLjg0Mi42NTggMy40MjEgMS45NzQgNC42NzFzMy43NSAyLjUgNy4yMzcgMy42MThsOS4zNDIgMi45NmM0LjczNiAxLjUxNCA4LjE1OCAzLjYxOSAxMC4xOTcgNi4zMTdzMy4wMjYgNS43ODkgMy4wMjYgOS4yMWMwIDIuODI5LS41OTIgNS4zOTUtMS43MSA3LjYzMmMtMS4xODQgMi4yMzctMi43NjMgNC4yMS00LjgwMyA1Ljc4OWMtMi4wMzkgMS42NDUtNC40NzQgMi44MjktNy4zMDIgMy42ODVjLTIuOTYxLjkyMS02LjA1MyAxLjM4MS05LjQwOCAxLjM4MSIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIwNy44MzcgMTYyLjgxNmMtMjEuNjQ1IDE1Ljk4Ny01My4wOTIgMjQuNDc0LTgwLjEzMiAyNC40NzRjLTM3Ljg5NCAwLTcyLjA0LTE0LjAxNC05Ny44MjktMzcuMzAzYy0yLjA0LTEuODQyLS4xOTctNC4zNDIgMi4yMzctMi44OTVjMjcuODk1IDE2LjE4NCA2Mi4zMDMgMjUuOTg3IDk3Ljg5NSAyNS45ODdjMjQuMDEzIDAgNTAuMzk1LTUgNzQuNjcxLTE1LjI2M2MzLjYxOC0xLjY0NSA2LjcxIDIuMzY4IDMuMTU4IDUiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIxNi44NSAxNTIuNTUzYy0yLjc2My0zLjU1My0xOC4yODktMS43MTEtMjUuMzI5LS44NTZjLTIuMTA1LjI2NC0yLjQzNC0xLjU3OS0uNTI2LTIuOTZjMTIuMzY4LTguNjg0IDMyLjY5Ny02LjE4NCAzNS4wNjYtMy4yOWMyLjM2OCAyLjk2MS0uNjU4IDIzLjI5LTEyLjIzNyAzMy4wMjdjLTEuNzc3IDEuNTEzLTMuNDg3LjcyMy0yLjY5OC0xLjI1YzIuNjMyLTYuNTEzIDguNDg3LTIxLjE4NSA1LjcyNC0yNC42NzEiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=", - resources: 126, - added: "Jan 30, 2024", - }, - { - id: 18, - account: "aws", - provider_id: 740350143861, - connected: true, - group: "Testing", - scan_status: "active", - scan_status_time: "8 minutes, 5 seconds", - last_scan: "Jul 16, 2024 11:46pm UTC", - next_scan: "Jul 17, 2024 11:46pm UTC", - provider_img: - "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgdmlld0JveD0iMCAwIDI1NiAyNTYiPjxnIGZpbGw9Im5vbmUiPjxyZWN0IHdpZHRoPSIyNTYiIGhlaWdodD0iMjU2IiBmaWxsPSIjZjRmMmVkIiByeD0iNjAiLz48cGF0aCBmaWxsPSIjMjUyZjNlIiBkPSJNODQuNzQ1IDExMS45NjFjMCAyLjQzNC4yNjMgNC40MDcuNzIzIDUuODU1YTM1IDM1IDAgMCAwIDIuMTA2IDQuNzM3Yy4zMjkuNTI2LjQ2IDEuMDUyLjQ2IDEuNTEzYzAgLjY1OC0uMzk1IDEuMzE2LTEuMjUgMS45NzNsLTQuMTQ1IDIuNzY0Yy0uNTkyLjM5NC0xLjE4NC41OTItMS43MS41OTJjLS42NTggMC0xLjMxNi0uMzI5LTEuOTc0LS45MjFhMjAuNCAyMC40IDAgMCAxLTIuMzY4LTMuMDkyYTUxIDUxIDAgMCAxLTIuMDQtMy44ODJxLTcuNjk3IDkuMDgtMTkuMzQyIDkuMDc5Yy01LjUyNiAwLTkuOTM0LTEuNTc5LTEzLjE1OC00LjczN2MtMy4yMjMtMy4xNTgtNC44NjgtNy4zNjgtNC44NjgtMTIuNjMxYzAtNS41OTMgMS45NzQtMTAuMTMyIDUuOTg3LTEzLjU1M3M5LjM0Mi01LjEzMiAxNi4xMTgtNS4xMzJjMi4yMzcgMCA0LjU0LjE5OCA2Ljk3NC41MjdzNC45MzQuODU1IDcuNTY2IDEuNDQ3di00LjgwM2MwLTUtMS4wNTMtOC40ODctMy4wOTItMTAuNTI2Yy0yLjEwNi0yLjA0LTUuNjU4LTMuMDI2LTEwLjcyNC0zLjAyNmMtMi4zMDMgMC00LjY3MS4yNjMtNy4xMDUuODU1cy00LjgwMyAxLjMxNi03LjEwNiAyLjIzN2ExOSAxOSAwIDAgMS0yLjMwMi44NTVjLS40Ni4xMzItLjc5LjE5OC0xLjA1My4xOThjLS45MiAwLTEuMzgyLS42NTgtMS4zODItMi4wNHYtMy4yMjRjMC0xLjA1Mi4xMzItMS44NDIuNDYxLTIuMzAycy45MjEtLjkyMSAxLjg0Mi0xLjM4MnEzLjQ1NC0xLjc3NiA4LjI5LTIuOTZjMy4yMjMtLjg1NiA2LjY0NC0xLjI1IDEwLjI2My0xLjI1YzcuODI5IDAgMTMuNTUyIDEuNzc2IDE3LjIzNyA1LjMyOGMzLjYxOCAzLjU1MyA1LjQ2IDguOTQ4IDUuNDYgMTYuMTg1djIxLjMxNnptLTI2LjcxIDEwYzIuMTcgMCA0LjQwNy0uMzk1IDYuNzc2LTEuMTg1YzIuMzY4LS43ODkgNC40NzMtMi4yMzcgNi4yNS00LjIxYzEuMDUyLTEuMjUgMS44NDItMi42MzIgMi4yMzYtNC4yMTFzLjY1OC0zLjQ4Ny42NTgtNS43MjN2LTIuNzY0YTU1IDU1IDAgMCAwLTYuMDUyLTEuMTE4YTUwIDUwIDAgMCAwLTYuMTg1LS4zOTVjLTQuNDA4IDAtNy42MzEuODU2LTkuODAyIDIuNjMycy0zLjIyNCA0LjI3Ni0zLjIyNCA3LjU2NmMwIDMuMDkyLjc5IDUuMzk0IDIuNDM0IDYuOTczYzEuNTggMS42NDUgMy44ODIgMi40MzUgNi45MDggMi40MzVtNTIuODI4IDcuMTA1Yy0xLjE4NCAwLTEuOTc0LS4xOTgtMi41LS42NThjLS41MjYtLjM5NS0uOTg3LTEuMzE2LTEuMzgxLTIuNTY2bC0xNS40Ni01MC44NTVjLS4zOTYtMS4zMTYtLjU5My0yLjE3MS0uNTkzLTIuNjMyYzAtMS4wNTIuNTI2LTEuNjQ1IDEuNTc5LTEuNjQ1aDYuNDQ3YzEuMjUgMCAyLjEwNi4xOTggMi41NjYuNjU4Yy41MjYuMzk1LjkyMSAxLjMxNiAxLjMxNiAyLjU2NmwxMS4wNTIgNDMuNTUzbDEwLjI2NC00My41NTNjLjMyOS0xLjMxNi43MjMtMi4xNyAxLjI1LTIuNTY2Yy41MjYtLjM5NCAxLjQ0Ny0uNjU3IDIuNjMxLS42NTdoNS4yNjNjMS4yNSAwIDIuMTA2LjE5NyAyLjYzMi42NTdjLjUyNi4zOTUuOTg3IDEuMzE2IDEuMjUgMi41NjZsMTAuMzk1IDQ0LjA3OWwxMS4zODEtNDQuMDc5Yy4zOTUtMS4zMTYuODU2LTIuMTcgMS4zMTYtMi41NjZjLjUyNi0uMzk0IDEuMzgyLS42NTcgMi41NjYtLjY1N2g2LjExOGMxLjA1MyAwIDEuNjQ1LjUyNiAxLjY0NSAxLjY0NGMwIC4zMy0uMDY2LjY1OC0uMTMyIDEuMDUzYy0uMDY1LjM5NS0uMTk3LjkyLS40NiAxLjY0NWwtMTUuODU1IDUwLjg1NXEtLjU5MyAxLjk3NC0xLjM4MiAyLjU2NmMtLjUyNi4zOTQtMS4zODIuNjU4LTIuNS42NThoLTUuNjU4Yy0xLjI1IDAtMi4xMDUtLjE5OC0yLjYzMS0uNjU4Yy0uNTI3LS40NjEtLjk4Ny0xLjMxNi0xLjI1LTIuNjMybC0xMC4xOTgtNDIuNDM0bC0xMC4xMzEgNDIuMzY4Yy0uMzI5IDEuMzE2LS43MjQgMi4xNzEtMS4yNSAyLjYzMmMtLjUyNy40Ni0xLjQ0OC42NTgtMi42MzIuNjU4em04NC41NCAxLjc3NmMtMy40MjEgMC02Ljg0Mi0uMzk1LTEwLjEzMi0xLjE4NGMtMy4yODktLjc5LTUuODU1LTEuNjQ1LTcuNTY2LTIuNjMyYy0xLjA1Mi0uNTkyLTEuNzc2LTEuMjUtMi4wMzktMS44NDJhNC42NSA0LjY1IDAgMCAxLS4zOTUtMS44NDJ2LTMuMzU1YzAtMS4zODIuNTI2LTIuMDQgMS41MTMtMi4wNHEuNTkzIDAgMS4xODQuMTk4Yy4zOTUuMTMxLjk4Ny4zOTQgMS42NDUuNjU4YTM1LjggMzUuOCAwIDAgMCA3LjIzNyAyLjMwMmEzOS41IDM5LjUgMCAwIDAgNy44MjkuNzljNC4xNDUgMCA3LjM2OC0uNzI0IDkuNjA1LTIuMTcxYzIuMjM3LTEuNDQ4IDMuNDIxLTMuNTUzIDMuNDIxLTYuMjVjMC0xLjg0Mi0uNTkyLTMuMzU2LTEuNzc2LTQuNjA2cy0zLjQyMS0yLjM2OC02LjY0NS0zLjQyMWwtOS41MzktMi45NmMtNC44MDMtMS41MTMtOC4zNTYtMy43NS0xMC41MjctNi43MWMtMi4xNzEtMi44OTUtMy4yODktNi4xMi0zLjI4OS05LjU0cTAtNC4xNDQgMS43NzYtNy4zMDNjMS4xODQtMi4xMDUgMi43NjMtMy45NDcgNC43MzctNS4zOTRjMS45NzQtMS41MTQgNC4yMTEtMi42MzIgNi44NDItMy40MjJjMi42MzItLjc5IDUuMzk1LTEuMTE4IDguMjktMS4xMThjMS40NDcgMCAyLjk2LjA2NiA0LjQwOC4yNjNjMS41MTMuMTk3IDIuODk0LjQ2IDQuMjc2LjcyNGMxLjMxNi4zMjkgMi41NjYuNjU4IDMuNzUgMS4wNTNxMS43NzYuNTkxIDIuNzYzIDEuMTg0Yy45MjEuNTI2IDEuNTc5IDEuMDUyIDEuOTc0IDEuNjQ0cS41OTIuNzkuNTkyIDIuMTcydjMuMDkyYzAgMS4zODEtLjUyNiAyLjEwNS0xLjUxMyAyLjEwNWMtLjUyNyAwLTEuMzgyLS4yNjMtMi41LS43OXEtNS42MjUtMi41NjUtMTIuNjMyLTIuNTY1Yy0zLjc1IDAtNi43MS41OTItOC43NSAxLjg0MnMtMy4wOTIgMy4xNTgtMy4wOTIgNS44NTVjMCAxLjg0Mi42NTggMy40MjEgMS45NzQgNC42NzFzMy43NSAyLjUgNy4yMzcgMy42MThsOS4zNDIgMi45NmM0LjczNiAxLjUxNCA4LjE1OCAzLjYxOSAxMC4xOTcgNi4zMTdzMy4wMjYgNS43ODkgMy4wMjYgOS4yMWMwIDIuODI5LS41OTIgNS4zOTUtMS43MSA3LjYzMmMtMS4xODQgMi4yMzctMi43NjMgNC4yMS00LjgwMyA1Ljc4OWMtMi4wMzkgMS42NDUtNC40NzQgMi44MjktNy4zMDIgMy42ODVjLTIuOTYxLjkyMS02LjA1MyAxLjM4MS05LjQwOCAxLjM4MSIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIwNy44MzcgMTYyLjgxNmMtMjEuNjQ1IDE1Ljk4Ny01My4wOTIgMjQuNDc0LTgwLjEzMiAyNC40NzRjLTM3Ljg5NCAwLTcyLjA0LTE0LjAxNC05Ny44MjktMzcuMzAzYy0yLjA0LTEuODQyLS4xOTctNC4zNDIgMi4yMzctMi44OTVjMjcuODk1IDE2LjE4NCA2Mi4zMDMgMjUuOTg3IDk3Ljg5NSAyNS45ODdjMjQuMDEzIDAgNTAuMzk1LTUgNzQuNjcxLTE1LjI2M2MzLjYxOC0xLjY0NSA2LjcxIDIuMzY4IDMuMTU4IDUiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIxNi44NSAxNTIuNTUzYy0yLjc2My0zLjU1My0xOC4yODktMS43MTEtMjUuMzI5LS44NTZjLTIuMTA1LjI2NC0yLjQzNC0xLjU3OS0uNTI2LTIuOTZjMTIuMzY4LTguNjg0IDMyLjY5Ny02LjE4NCAzNS4wNjYtMy4yOWMyLjM2OCAyLjk2MS0uNjU4IDIzLjI5LTEyLjIzNyAzMy4wMjdjLTEuNzc3IDEuNTEzLTMuNDg3LjcyMy0yLjY5OC0xLjI1YzIuNjMyLTYuNTEzIDguNDg3LTIxLjE4NSA1LjcyNC0yNC42NzEiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=", - resources: 142, - added: "Mar 19, 2024", - }, - { - id: 19, - account: "aws", - provider_id: 740350143862, - connected: false, - group: "Information Technology", - scan_status: "paused", - scan_status_time: "14 minutes, 45 seconds", - last_scan: "Jul 16, 2024 10:46pm UTC", - next_scan: "Jul 17, 2024 10:46pm UTC", - provider_img: - "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgdmlld0JveD0iMCAwIDI1NiAyNTYiPjxnIGZpbGw9Im5vbmUiPjxyZWN0IHdpZHRoPSIyNTYiIGhlaWdodD0iMjU2IiBmaWxsPSIjZjRmMmVkIiByeD0iNjAiLz48cGF0aCBmaWxsPSIjMjUyZjNlIiBkPSJNODQuNzQ1IDExMS45NjFjMCAyLjQzNC4yNjMgNC40MDcuNzIzIDUuODU1YTM1IDM1IDAgMCAwIDIuMTA2IDQuNzM3Yy4zMjkuNTI2LjQ2IDEuMDUyLjQ2IDEuNTEzYzAgLjY1OC0uMzk1IDEuMzE2LTEuMjUgMS45NzNsLTQuMTQ1IDIuNzY0Yy0uNTkyLjM5NC0xLjE4NC41OTItMS43MS41OTJjLS42NTggMC0xLjMxNi0uMzI5LTEuOTc0LS45MjFhMjAuNCAyMC40IDAgMCAxLTIuMzY4LTMuMDkyYTUxIDUxIDAgMCAxLTIuMDQtMy44ODJxLTcuNjk3IDkuMDgtMTkuMzQyIDkuMDc5Yy01LjUyNiAwLTkuOTM0LTEuNTc5LTEzLjE1OC00LjczN2MtMy4yMjMtMy4xNTgtNC44NjgtNy4zNjgtNC44NjgtMTIuNjMxYzAtNS41OTMgMS45NzQtMTAuMTMyIDUuOTg3LTEzLjU1M3M5LjM0Mi01LjEzMiAxNi4xMTgtNS4xMzJjMi4yMzcgMCA0LjU0LjE5OCA2Ljk3NC41MjdzNC45MzQuODU1IDcuNTY2IDEuNDQ3di00LjgwM2MwLTUtMS4wNTMtOC40ODctMy4wOTItMTAuNTI2Yy0yLjEwNi0yLjA0LTUuNjU4LTMuMDI2LTEwLjcyNC0zLjAyNmMtMi4zMDMgMC00LjY3MS4yNjMtNy4xMDUuODU1cy00LjgwMyAxLjMxNi03LjEwNiAyLjIzN2ExOSAxOSAwIDAgMS0yLjMwMi44NTVjLS40Ni4xMzItLjc5LjE5OC0xLjA1My4xOThjLS45MiAwLTEuMzgyLS42NTgtMS4zODItMi4wNHYtMy4yMjRjMC0xLjA1Mi4xMzItMS44NDIuNDYxLTIuMzAycy45MjEtLjkyMSAxLjg0Mi0xLjM4MnEzLjQ1NC0xLjc3NiA4LjI5LTIuOTZjMy4yMjMtLjg1NiA2LjY0NC0xLjI1IDEwLjI2My0xLjI1YzcuODI5IDAgMTMuNTUyIDEuNzc2IDE3LjIzNyA1LjMyOGMzLjYxOCAzLjU1MyA1LjQ2IDguOTQ4IDUuNDYgMTYuMTg1djIxLjMxNnptLTI2LjcxIDEwYzIuMTcgMCA0LjQwNy0uMzk1IDYuNzc2LTEuMTg1YzIuMzY4LS43ODkgNC40NzMtMi4yMzcgNi4yNS00LjIxYzEuMDUyLTEuMjUgMS44NDItMi42MzIgMi4yMzYtNC4yMTFzLjY1OC0zLjQ4Ny42NTgtNS43MjN2LTIuNzY0YTU1IDU1IDAgMCAwLTYuMDUyLTEuMTE4YTUwIDUwIDAgMCAwLTYuMTg1LS4zOTVjLTQuNDA4IDAtNy42MzEuODU2LTkuODAyIDIuNjMycy0zLjIyNCA0LjI3Ni0zLjIyNCA3LjU2NmMwIDMuMDkyLjc5IDUuMzk0IDIuNDM0IDYuOTczYzEuNTggMS42NDUgMy44ODIgMi40MzUgNi45MDggMi40MzVtNTIuODI4IDcuMTA1Yy0xLjE4NCAwLTEuOTc0LS4xOTgtMi41LS42NThjLS41MjYtLjM5NS0uOTg3LTEuMzE2LTEuMzgxLTIuNTY2bC0xNS40Ni01MC44NTVjLS4zOTYtMS4zMTYtLjU5My0yLjE3MS0uNTkzLTIuNjMyYzAtMS4wNTIuNTI2LTEuNjQ1IDEuNTc5LTEuNjQ1aDYuNDQ3YzEuMjUgMCAyLjEwNi4xOTggMi41NjYuNjU4Yy41MjYuMzk1LjkyMSAxLjMxNiAxLjMxNiAyLjU2NmwxMS4wNTIgNDMuNTUzbDEwLjI2NC00My41NTNjLjMyOS0xLjMxNi43MjMtMi4xNyAxLjI1LTIuNTY2Yy41MjYtLjM5NCAxLjQ0Ny0uNjU3IDIuNjMxLS42NTdoNS4yNjNjMS4yNSAwIDIuMTA2LjE5NyAyLjYzMi42NTdjLjUyNi4zOTUuOTg3IDEuMzE2IDEuMjUgMi41NjZsMTAuMzk1IDQ0LjA3OWwxMS4zODEtNDQuMDc5Yy4zOTUtMS4zMTYuODU2LTIuMTcgMS4zMTYtMi41NjZjLjUyNi0uMzk0IDEuMzgyLS42NTcgMi41NjYtLjY1N2g2LjExOGMxLjA1MyAwIDEuNjQ1LjUyNiAxLjY0NSAxLjY0NGMwIC4zMy0uMDY2LjY1OC0uMTMyIDEuMDUzYy0uMDY1LjM5NS0uMTk3LjkyLS40NiAxLjY0NWwtMTUuODU1IDUwLjg1NXEtLjU5MyAxLjk3NC0xLjM4MiAyLjU2NmMtLjUyNi4zOTQtMS4zODIuNjU4LTIuNS42NThoLTUuNjU4Yy0xLjI1IDAtMi4xMDUtLjE5OC0yLjYzMS0uNjU4Yy0uNTI3LS40NjEtLjk4Ny0xLjMxNi0xLjI1LTIuNjMybC0xMC4xOTgtNDIuNDM0bC0xMC4xMzEgNDIuMzY4Yy0uMzI5IDEuMzE2LS43MjQgMi4xNzEtMS4yNSAyLjYzMmMtLjUyNy40Ni0xLjQ0OC42NTgtMi42MzIuNjU4em04NC41NCAxLjc3NmMtMy40MjEgMC02Ljg0Mi0uMzk1LTEwLjEzMi0xLjE4NGMtMy4yODktLjc5LTUuODU1LTEuNjQ1LTcuNTY2LTIuNjMyYy0xLjA1Mi0uNTkyLTEuNzc2LTEuMjUtMi4wMzktMS44NDJhNC42NSA0LjY1IDAgMCAxLS4zOTUtMS44NDJ2LTMuMzU1YzAtMS4zODIuNTI2LTIuMDQgMS41MTMtMi4wNHEuNTkzIDAgMS4xODQuMTk4Yy4zOTUuMTMxLjk4Ny4zOTQgMS42NDUuNjU4YTM1LjggMzUuOCAwIDAgMCA3LjIzNyAyLjMwMmEzOS41IDM5LjUgMCAwIDAgNy44MjkuNzljNC4xNDUgMCA3LjM2OC0uNzI0IDkuNjA1LTIuMTcxYzIuMjM3LTEuNDQ4IDMuNDIxLTMuNTUzIDMuNDIxLTYuMjVjMC0xLjg0Mi0uNTkyLTMuMzU2LTEuNzc2LTQuNjA2cy0zLjQyMS0yLjM2OC02LjY0NS0zLjQyMWwtOS41MzktMi45NmMtNC44MDMtMS41MTMtOC4zNTYtMy43NS0xMC41MjctNi43MWMtMi4xNzEtMi44OTUtMy4yODktNi4xMi0zLjI4OS05LjU0cTAtNC4xNDQgMS43NzYtNy4zMDNjMS4xODQtMi4xMDUgMi43NjMtMy45NDcgNC43MzctNS4zOTRjMS45NzQtMS41MTQgNC4yMTEtMi42MzIgNi44NDItMy40MjJjMi42MzItLjc5IDUuMzk1LTEuMTE4IDguMjktMS4xMThjMS40NDcgMCAyLjk2LjA2NiA0LjQwOC4yNjNjMS41MTMuMTk3IDIuODk0LjQ2IDQuMjc2LjcyNGMxLjMxNi4zMjkgMi41NjYuNjU4IDMuNzUgMS4wNTNxMS43NzYuNTkxIDIuNzYzIDEuMTg0Yy45MjEuNTI2IDEuNTc5IDEuMDUyIDEuOTc0IDEuNjQ0cS41OTIuNzkuNTkyIDIuMTcydjMuMDkyYzAgMS4zODEtLjUyNiAyLjEwNS0xLjUxMyAyLjEwNWMtLjUyNyAwLTEuMzgyLS4yNjMtMi41LS43OXEtNS42MjUtMi41NjUtMTIuNjMyLTIuNTY1Yy0zLjc1IDAtNi43MS41OTItOC43NSAxLjg0MnMtMy4wOTIgMy4xNTgtMy4wOTIgNS44NTVjMCAxLjg0Mi42NTggMy40MjEgMS45NzQgNC42NzFzMy43NSAyLjUgNy4yMzcgMy42MThsOS4zNDIgMi45NmM0LjczNiAxLjUxNCA4LjE1OCAzLjYxOSAxMC4xOTcgNi4zMTdzMy4wMjYgNS43ODkgMy4wMjYgOS4yMWMwIDIuODI5LS41OTIgNS4zOTUtMS43MSA3LjYzMmMtMS4xODQgMi4yMzctMi43NjMgNC4yMS00LjgwMyA1Ljc4OWMtMi4wMzkgMS42NDUtNC40NzQgMi44MjktNy4zMDIgMy42ODVjLTIuOTYxLjkyMS02LjA1MyAxLjM4MS05LjQwOCAxLjM4MSIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIwNy44MzcgMTYyLjgxNmMtMjEuNjQ1IDE1Ljk4Ny01My4wOTIgMjQuNDc0LTgwLjEzMiAyNC40NzRjLTM3Ljg5NCAwLTcyLjA0LTE0LjAxNC05Ny44MjktMzcuMzAzYy0yLjA0LTEuODQyLS4xOTctNC4zNDIgMi4yMzctMi44OTVjMjcuODk1IDE2LjE4NCA2Mi4zMDMgMjUuOTg3IDk3Ljg5NSAyNS45ODdjMjQuMDEzIDAgNTAuMzk1LTUgNzQuNjcxLTE1LjI2M2MzLjYxOC0xLjY0NSA2LjcxIDIuMzY4IDMuMTU4IDUiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIxNi44NSAxNTIuNTUzYy0yLjc2My0zLjU1My0xOC4yODktMS43MTEtMjUuMzI5LS44NTZjLTIuMTA1LjI2NC0yLjQzNC0xLjU3OS0uNTI2LTIuOTZjMTIuMzY4LTguNjg0IDMyLjY5Ny02LjE4NCAzNS4wNjYtMy4yOWMyLjM2OCAyLjk2MS0uNjU4IDIzLjI5LTEyLjIzNyAzMy4wMjdjLTEuNzc3IDEuNTEzLTMuNDg3LjcyMy0yLjY5OC0xLjI1YzIuNjMyLTYuNTEzIDguNDg3LTIxLjE4NSA1LjcyNC0yNC42NzEiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=", - resources: 130, - added: "Feb 23, 2024", - }, - { - id: 20, - account: "aws", - provider_id: 740350143863, - connected: true, - group: "Operations", - scan_status: "active", - scan_status_time: "7 minutes, 20 seconds", - last_scan: "Jul 16, 2024 9:46pm UTC", - next_scan: "Jul 17, 2024 9:46pm UTC", - provider_img: - "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgdmlld0JveD0iMCAwIDI1NiAyNTYiPjxnIGZpbGw9Im5vbmUiPjxyZWN0IHdpZHRoPSIyNTYiIGhlaWdodD0iMjU2IiBmaWxsPSIjZjRmMmVkIiByeD0iNjAiLz48cGF0aCBmaWxsPSIjMjUyZjNlIiBkPSJNODQuNzQ1IDExMS45NjFjMCAyLjQzNC4yNjMgNC40MDcuNzIzIDUuODU1YTM1IDM1IDAgMCAwIDIuMTA2IDQuNzM3Yy4zMjkuNTI2LjQ2IDEuMDUyLjQ2IDEuNTEzYzAgLjY1OC0uMzk1IDEuMzE2LTEuMjUgMS45NzNsLTQuMTQ1IDIuNzY0Yy0uNTkyLjM5NC0xLjE4NC41OTItMS43MS41OTJjLS42NTggMC0xLjMxNi0uMzI5LTEuOTc0LS45MjFhMjAuNCAyMC40IDAgMCAxLTIuMzY4LTMuMDkyYTUxIDUxIDAgMCAxLTIuMDQtMy44ODJxLTcuNjk3IDkuMDgtMTkuMzQyIDkuMDc5Yy01LjUyNiAwLTkuOTM0LTEuNTc5LTEzLjE1OC00LjczN2MtMy4yMjMtMy4xNTgtNC44NjgtNy4zNjgtNC44NjgtMTIuNjMxYzAtNS41OTMgMS45NzQtMTAuMTMyIDUuOTg3LTEzLjU1M3M5LjM0Mi01LjEzMiAxNi4xMTgtNS4xMzJjMi4yMzcgMCA0LjU0LjE5OCA2Ljk3NC41MjdzNC45MzQuODU1IDcuNTY2IDEuNDQ3di00LjgwM2MwLTUtMS4wNTMtOC40ODctMy4wOTItMTAuNTI2Yy0yLjEwNi0yLjA0LTUuNjU4LTMuMDI2LTEwLjcyNC0zLjAyNmMtMi4zMDMgMC00LjY3MS4yNjMtNy4xMDUuODU1cy00LjgwMyAxLjMxNi03LjEwNiAyLjIzN2ExOSAxOSAwIDAgMS0yLjMwMi44NTVjLS40Ni4xMzItLjc5LjE5OC0xLjA1My4xOThjLS45MiAwLTEuMzgyLS42NTgtMS4zODItMi4wNHYtMy4yMjRjMC0xLjA1Mi4xMzItMS44NDIuNDYxLTIuMzAycy45MjEtLjkyMSAxLjg0Mi0xLjM4MnEzLjQ1NC0xLjc3NiA4LjI5LTIuOTZjMy4yMjMtLjg1NiA2LjY0NC0xLjI1IDEwLjI2My0xLjI1YzcuODI5IDAgMTMuNTUyIDEuNzc2IDE3LjIzNyA1LjMyOGMzLjYxOCAzLjU1MyA1LjQ2IDguOTQ4IDUuNDYgMTYuMTg1djIxLjMxNnptLTI2LjcxIDEwYzIuMTcgMCA0LjQwNy0uMzk1IDYuNzc2LTEuMTg1YzIuMzY4LS43ODkgNC40NzMtMi4yMzcgNi4yNS00LjIxYzEuMDUyLTEuMjUgMS44NDItMi42MzIgMi4yMzYtNC4yMTFzLjY1OC0zLjQ4Ny42NTgtNS43MjN2LTIuNzY0YTU1IDU1IDAgMCAwLTYuMDUyLTEuMTE4YTUwIDUwIDAgMCAwLTYuMTg1LS4zOTVjLTQuNDA4IDAtNy42MzEuODU2LTkuODAyIDIuNjMycy0zLjIyNCA0LjI3Ni0zLjIyNCA3LjU2NmMwIDMuMDkyLjc5IDUuMzk0IDIuNDM0IDYuOTczYzEuNTggMS42NDUgMy44ODIgMi40MzUgNi45MDggMi40MzVtNTIuODI4IDcuMTA1Yy0xLjE4NCAwLTEuOTc0LS4xOTgtMi41LS42NThjLS41MjYtLjM5NS0uOTg3LTEuMzE2LTEuMzgxLTIuNTY2bC0xNS40Ni01MC44NTVjLS4zOTYtMS4zMTYtLjU5My0yLjE3MS0uNTkzLTIuNjMyYzAtMS4wNTIuNTI2LTEuNjQ1IDEuNTc5LTEuNjQ1aDYuNDQ3YzEuMjUgMCAyLjEwNi4xOTggMi41NjYuNjU4Yy41MjYuMzk1LjkyMSAxLjMxNiAxLjMxNiAyLjU2NmwxMS4wNTIgNDMuNTUzbDEwLjI2NC00My41NTNjLjMyOS0xLjMxNi43MjMtMi4xNyAxLjI1LTIuNTY2Yy41MjYtLjM5NCAxLjQ0Ny0uNjU3IDIuNjMxLS42NTdoNS4yNjNjMS4yNSAwIDIuMTA2LjE5NyAyLjYzMi42NTdjLjUyNi4zOTUuOTg3IDEuMzE2IDEuMjUgMi41NjZsMTAuMzk1IDQ0LjA3OWwxMS4zODEtNDQuMDc5Yy4zOTUtMS4zMTYuODU2LTIuMTcgMS4zMTYtMi41NjZjLjUyNi0uMzk0IDEuMzgyLS42NTcgMi41NjYtLjY1N2g2LjExOGMxLjA1MyAwIDEuNjQ1LjUyNiAxLjY0NSAxLjY0NGMwIC4zMy0uMDY2LjY1OC0uMTMyIDEuMDUzYy0uMDY1LjM5NS0uMTk3LjkyLS40NiAxLjY0NWwtMTUuODU1IDUwLjg1NXEtLjU5MyAxLjk3NC0xLjM4MiAyLjU2NmMtLjUyNi4zOTQtMS4zODIuNjU4LTIuNS42NThoLTUuNjU4Yy0xLjI1IDAtMi4xMDUtLjE5OC0yLjYzMS0uNjU4Yy0uNTI3LS40NjEtLjk4Ny0xLjMxNi0xLjI1LTIuNjMybC0xMC4xOTgtNDIuNDM0bC0xMC4xMzEgNDIuMzY4Yy0uMzI5IDEuMzE2LS43MjQgMi4xNzEtMS4yNSAyLjYzMmMtLjUyNy40Ni0xLjQ0OC42NTgtMi42MzIuNjU4em04NC41NCAxLjc3NmMtMy40MjEgMC02Ljg0Mi0uMzk1LTEwLjEzMi0xLjE4NGMtMy4yODktLjc5LTUuODU1LTEuNjQ1LTcuNTY2LTIuNjMyYy0xLjA1Mi0uNTkyLTEuNzc2LTEuMjUtMi4wMzktMS44NDJhNC42NSA0LjY1IDAgMCAxLS4zOTUtMS44NDJ2LTMuMzU1YzAtMS4zODIuNTI2LTIuMDQgMS41MTMtMi4wNHEuNTkzIDAgMS4xODQuMTk4Yy4zOTUuMTMxLjk4Ny4zOTQgMS42NDUuNjU4YTM1LjggMzUuOCAwIDAgMCA3LjIzNyAyLjMwMmEzOS41IDM5LjUgMCAwIDAgNy44MjkuNzljNC4xNDUgMCA3LjM2OC0uNzI0IDkuNjA1LTIuMTcxYzIuMjM3LTEuNDQ4IDMuNDIxLTMuNTUzIDMuNDIxLTYuMjVjMC0xLjg0Mi0uNTkyLTMuMzU2LTEuNzc2LTQuNjA2cy0zLjQyMS0yLjM2OC02LjY0NS0zLjQyMWwtOS41MzktMi45NmMtNC44MDMtMS41MTMtOC4zNTYtMy43NS0xMC41MjctNi43MWMtMi4xNzEtMi44OTUtMy4yODktNi4xMi0zLjI4OS05LjU0cTAtNC4xNDQgMS43NzYtNy4zMDNjMS4xODQtMi4xMDUgMi43NjMtMy45NDcgNC43MzctNS4zOTRjMS45NzQtMS41MTQgNC4yMTEtMi42MzIgNi44NDItMy40MjJjMi42MzItLjc5IDUuMzk1LTEuMTE4IDguMjktMS4xMThjMS40NDcgMCAyLjk2LjA2NiA0LjQwOC4yNjNjMS41MTMuMTk3IDIuODk0LjQ2IDQuMjc2LjcyNGMxLjMxNi4zMjkgMi41NjYuNjU4IDMuNzUgMS4wNTNxMS43NzYuNTkxIDIuNzYzIDEuMTg0Yy45MjEuNTI2IDEuNTc5IDEuMDUyIDEuOTc0IDEuNjQ0cS41OTIuNzkuNTkyIDIuMTcydjMuMDkyYzAgMS4zODEtLjUyNiAyLjEwNS0xLjUxMyAyLjEwNWMtLjUyNyAwLTEuMzgyLS4yNjMtMi41LS43OXEtNS42MjUtMi41NjUtMTIuNjMyLTIuNTY1Yy0zLjc1IDAtNi43MS41OTItOC43NSAxLjg0MnMtMy4wOTIgMy4xNTgtMy4wOTIgNS44NTVjMCAxLjg0Mi42NTggMy40MjEgMS45NzQgNC42NzFzMy43NSAyLjUgNy4yMzcgMy42MThsOS4zNDIgMi45NmM0LjczNiAxLjUxNCA4LjE1OCAzLjYxOSAxMC4xOTcgNi4zMTdzMy4wMjYgNS43ODkgMy4wMjYgOS4yMWMwIDIuODI5LS41OTIgNS4zOTUtMS43MSA3LjYzMmMtMS4xODQgMi4yMzctMi43NjMgNC4yMS00LjgwMyA1Ljc4OWMtMi4wMzkgMS42NDUtNC40NzQgMi44MjktNy4zMDIgMy42ODVjLTIuOTYxLjkyMS02LjA1MyAxLjM4MS05LjQwOCAxLjM4MSIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIwNy44MzcgMTYyLjgxNmMtMjEuNjQ1IDE1Ljk4Ny01My4wOTIgMjQuNDc0LTgwLjEzMiAyNC40NzRjLTM3Ljg5NCAwLTcyLjA0LTE0LjAxNC05Ny44MjktMzcuMzAzYy0yLjA0LTEuODQyLS4xOTctNC4zNDIgMi4yMzctMi44OTVjMjcuODk1IDE2LjE4NCA2Mi4zMDMgMjUuOTg3IDk3Ljg5NSAyNS45ODdjMjQuMDEzIDAgNTAuMzk1LTUgNzQuNjcxLTE1LjI2M2MzLjYxOC0xLjY0NSA2LjcxIDIuMzY4IDMuMTU4IDUiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIxNi44NSAxNTIuNTUzYy0yLjc2My0zLjU1My0xOC4yODktMS43MTEtMjUuMzI5LS44NTZjLTIuMTA1LjI2NC0yLjQzNC0xLjU3OS0uNTI2LTIuOTZjMTIuMzY4LTguNjg0IDMyLjY5Ny02LjE4NCAzNS4wNjYtMy4yOWMyLjM2OCAyLjk2MS0uNjU4IDIzLjI5LTEyLjIzNyAzMy4wMjdjLTEuNzc3IDEuNTEzLTMuNDg3LjcyMy0yLjY5OC0xLjI1YzIuNjMyLTYuNTEzIDguNDg3LTIxLjE4NSA1LjcyNC0yNC42NzEiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=", - resources: 132, - added: "Apr 1, 2024", - }, -]; diff --git a/components/providers/AccountInfo.tsx b/components/providers/ProviderInfo.tsx similarity index 76% rename from components/providers/AccountInfo.tsx rename to components/providers/ProviderInfo.tsx index 7cab849d54..bc1e90124a 100644 --- a/components/providers/AccountInfo.tsx +++ b/components/providers/ProviderInfo.tsx @@ -8,18 +8,18 @@ import { WifiOffIcon, } from "../icons"; -interface AccountInfoProps { +interface ProviderInfoProps { connected: boolean; provider: "aws" | "azure" | "gcp"; - accountName: string; - accountId: string; + providerAlias: string; + providerId: string; } -export const AccountInfo: React.FC = ({ +export const ProviderInfo: React.FC = ({ connected, provider, - accountName, - accountId, + providerAlias, + providerId, }) => { const getIcon = () => { return connected ? : ; @@ -45,8 +45,8 @@ export const AccountInfo: React.FC = ({
{getIcon()}
{getProviderLogo()}
- {accountName} - {accountId} + {providerAlias} + {providerId}
diff --git a/components/ui/table/CustomTable.tsx b/components/ui/table/CustomTable.tsx deleted file mode 100644 index cc6616344f..0000000000 --- a/components/ui/table/CustomTable.tsx +++ /dev/null @@ -1,406 +0,0 @@ -"use client"; - -import { - Button, - Chip, - Dropdown, - DropdownItem, - DropdownMenu, - DropdownTrigger, - // Input, - Pagination, - Table, - TableBody, - TableCell, - TableColumn, - TableHeader, - TableRow, - User, -} from "@nextui-org/react"; -import React from "react"; - -import { - columns, - Provider, - providers, - statusColorMap, - statusOptions, -} from "../../../app/(prowler)/providers/data"; -import { - // ChevronDownIcon, - PlusIcon, - // SearchIcon, - VerticalDotsIcon, -} from "../../icons/Icons"; - -interface CustomTableProps { - initialVisibleColumns: string[]; - initialRowsPerPage?: number; - selectionMode: "single" | "multiple" | "none"; -} - -type SortDirection = "ascending" | "descending"; - -interface SortDescriptor { - column: string; - direction: SortDirection; -} - -export const CustomTable: React.FC = ({ - initialVisibleColumns, - initialRowsPerPage = 5, - selectionMode = "none", -}) => { - const [filterValue, setFilterValue] = React.useState(""); - // const [selectedKeys, setSelectedKeys] = React.useState(new Set([])); - const selectedKeys = new Set([]); - - // const [visibleColumns, setVisibleColumns] = React.useState< - // string | Set - // >(new Set(initialVisibleColumns)); - - const visibleColumns = new Set(initialVisibleColumns); - - // const [statusFilter, setStatusFilter] = React.useState("all"); - const statusFilter: string = "all"; - - const [rowsPerPage, setRowsPerPage] = React.useState(initialRowsPerPage); - - // const [sortDescriptor, setSortDescriptor] = React.useState({ - // column: "age", - // direction: "ascending", - // }); - - const sortDescriptor: SortDescriptor = { - column: "age", - direction: "ascending", - }; - - const [page, setPage] = React.useState(1); - - const pages = Math.ceil(providers.length / rowsPerPage); - - const hasSearchFilter = Boolean(filterValue); - - const headerColumns = React.useMemo(() => { - // if (visibleColumns === "all") return columns; - - return columns.filter((column) => - Array.from(visibleColumns).includes(column.uid), - ); - }, [visibleColumns]); - - const filteredItems = React.useMemo(() => { - let filteredUsers = [...providers]; - - if (hasSearchFilter) { - filteredUsers = filteredUsers.filter((provider) => - provider.account.toLowerCase().includes(filterValue.toLowerCase()), - ); - } - if ( - statusFilter !== "all" && - Array.from(statusFilter).length !== statusOptions.length - ) { - filteredUsers = filteredUsers.filter((provider) => - Array.from(statusFilter).includes(provider.group), - ); - } - - return filteredUsers; - }, [providers, filterValue, statusFilter]); - - const items = React.useMemo(() => { - const start = (page - 1) * rowsPerPage; - const end = start + rowsPerPage; - - return filteredItems.slice(start, end); - }, [page, filteredItems, rowsPerPage]); - - const sortedItems = React.useMemo(() => { - // The type "any" for a and b will removed in the next iteration. - return [...items].sort((a: any, b: any) => { - const first = a[sortDescriptor.column]; - const second = b[sortDescriptor.column]; - const cmp: number = first < second ? -1 : first > second ? 1 : 0; - - return sortDescriptor.direction === "descending" ? -cmp : cmp; - }); - }, [sortDescriptor, items]); - - const renderCell = React.useCallback( - (provider: Provider, columnKey: keyof Provider) => { - // eslint-disable-next-line security/detect-object-injection - const cellValue = provider[columnKey]; - - switch (columnKey) { - case "account": - return ( - - ); - case "group": - return ( -
-

{cellValue}

-

- {provider.group} -

-
- ); - case "scan_status": - return ( - - {cellValue} - - ); - case "actions": - return ( -
- - - - - - Manage - Delete - - -
- ); - default: - return cellValue; - } - }, - [], - ); - - const onRowsPerPageChange = React.useCallback( - (e: React.ChangeEvent) => { - setRowsPerPage(Number(e.target.value)); - setPage(1); - }, - [], - ); - - const onSearchChange = React.useCallback((value: string) => { - if (value) { - setFilterValue(value); - setPage(1); - } else { - setFilterValue(""); - } - }, []); - - const topContent = React.useMemo(() => { - return ( -
-
- {/* } - value={filterValue} - variant="bordered" - onClear={() => setFilterValue("")} - onValueChange={onSearchChange} - /> */} -
- {/* - - - - - {statusOptions.map((status) => ( - - {capitalize(status.name)} - - ))} - - - - - - - - {columns.map((column) => ( - - {capitalize(column.name)} - - ))} - - */} - -
-
-
- - Total {providers.length} entries - - {items.length < initialRowsPerPage &&
hi hi que pasa
} - -
-
- ); - }, [ - filterValue, - statusFilter, - visibleColumns, - onSearchChange, - onRowsPerPageChange, - providers.length, - hasSearchFilter, - ]); - - const bottomContent = React.useMemo(() => { - return ( -
- - {/* {selectionMode !== "none" && ( - - {selectedKeys === "all" - ? "All items selected" - : `${selectedKeys.size} of ${items.length} selected`} - - )} */} -
- ); - }, [selectedKeys, items.length, page, pages, hasSearchFilter]); - - const classNames = React.useMemo( - () => ({ - wrapper: ["max-h-[382px]", "max-w-3xl"], - th: ["bg-transparent", "text-default-500", "border-b", "border-divider"], - td: [ - // changing the rows border radius - // first - "group-data-[first=true]:first:before:rounded-none", - "group-data-[first=true]:last:before:rounded-none", - // middle - "group-data-[middle=true]:before:rounded-none", - // last - "group-data-[last=true]:first:before:rounded-none", - "group-data-[last=true]:last:before:rounded-none", - ], - }), - [], - ); - - return ( - - - {(column) => ( - - {column.name} - - )} - - - {(item) => { - return ( - - {(columnKey) => ( - // @ts-expect-error: Disable type checking for this line - {renderCell(item, columnKey)} - )} - - ); - }} - -
- ); -}; From 0035c8c08e23c9669f805a3084e866716089746f Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Mon, 29 Jul 2024 12:26:25 +0200 Subject: [PATCH 066/411] refactor: improve sidebar display behavior adding a custom hook --- components/index.ts | 7 ++-- components/ui/sidebar/SidebarWrap.tsx | 29 ++++++----------- hooks/index.ts | 1 + hooks/useLocalStorage.ts | 46 +++++++++++++++++++++++++++ 4 files changed, 61 insertions(+), 22 deletions(-) create mode 100644 hooks/index.ts create mode 100644 hooks/useLocalStorage.ts diff --git a/components/index.ts b/components/index.ts index 0d1a39d806..f4acf6e356 100644 --- a/components/index.ts +++ b/components/index.ts @@ -1,7 +1,10 @@ -export * from "./providers/AccountInfo"; export * from "./providers/DateWithTime"; +export * from "./providers/ProviderInfo"; export * from "./providers/ScanStatus"; +export * from "./providers/table/columns"; +export * from "./providers/table/DataTable"; export * from "./ui/header/Header"; export * from "./ui/modal"; export * from "./ui/sidebar"; -export * from "./ui/table/CustomTable"; +export * from "./ui/table/StatusBadge"; +export * from "./ui/table/Table"; diff --git a/components/ui/sidebar/SidebarWrap.tsx b/components/ui/sidebar/SidebarWrap.tsx index e68958a446..bca4d50a36 100644 --- a/components/ui/sidebar/SidebarWrap.tsx +++ b/components/ui/sidebar/SidebarWrap.tsx @@ -4,9 +4,11 @@ import { Icon } from "@iconify/react"; import { Button, ScrollShadow, Spacer, Tooltip } from "@nextui-org/react"; import clsx from "clsx"; import { usePathname } from "next/navigation"; -import React, { useCallback, useEffect, useState } from "react"; +import React, { useCallback } from "react"; import { useMediaQuery } from "usehooks-ts"; +import { useLocalStorage } from "@/hooks/useLocalStorage"; + import { ProwlerExtended, ProwlerShort, @@ -17,31 +19,18 @@ import { sectionItemsWithTeams } from "./SidebarItems"; import { UserAvatar } from "./UserAvatar"; export const SidebarWrap = () => { - const [isCollapsed, setIsCollapsed] = useState( - !!localStorage.getItem("isCollapsed") ?? true, - ); - const pathname = usePathname(); - const currentPath = pathname === "/" ? "overview" : pathname.split("/")?.[1]; + const [isCollapsed, setIsCollapsed] = useLocalStorage("isCollapsed", true); const isMobile = useMediaQuery("(max-width: 768px)"); - const isCompact = isCollapsed || isMobile; - - useEffect(() => { - const savedState = localStorage.getItem("isCollapsed"); - if (savedState !== null) { - setIsCollapsed(JSON.parse(savedState)); - } - }, []); + const isCompact = Boolean(isCollapsed) || isMobile; const onToggle = useCallback(() => { - setIsCollapsed((prev) => { - const newState = !prev; - localStorage.setItem("isCollapsed", JSON.stringify(newState)); - return newState; - }); - }, []); + setIsCollapsed(!isCollapsed); + }, [isCollapsed]); + + const currentPath = pathname === "/" ? "overview" : pathname.split("/")?.[1]; return (
string | boolean), + ) => void, +] => { + const [state, setState] = useState(initialValue); + + useEffect(() => { + try { + const value = window.localStorage.getItem(key); + + if (value) { + setState(JSON.parse(value)); + } + } catch (error) { + console.log(error); + } + }, [key]); + + const setValue = ( + value: string | boolean | ((val: string | boolean) => string | boolean), + ) => { + try { + // If the passed value is a callback function, + // then call it with the existing state. + const valueToStore = + typeof value === "function" + ? (value as (val: string | boolean) => string | boolean)(state) + : value; + window.localStorage.setItem(key, JSON.stringify(valueToStore)); + setState(valueToStore); + } catch (error) { + console.log(error); + } + }; + + return [state, setValue]; +}; From 9fd642fe0e42cdd724ad42f7f007acdfcd10a605 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Mon, 29 Jul 2024 12:41:02 +0200 Subject: [PATCH 067/411] feat: the Providers table is rendering real data --- app/(prowler)/providers/page.tsx | 49 +++----- components/index.ts | 2 +- components/providers/DateWithTime.tsx | 2 +- .../providers/table/ColumnsProviders.tsx | 111 +++++++++++++++++ components/providers/table/DataTable.tsx | 109 ++++++++++++++++ components/ui/table/StatusBadge.tsx | 25 ++++ components/ui/table/Table.tsx | 117 ++++++++++++++++++ lib/actions/provider.actions.ts | 3 +- 8 files changed, 385 insertions(+), 33 deletions(-) create mode 100644 components/providers/table/ColumnsProviders.tsx create mode 100644 components/providers/table/DataTable.tsx create mode 100644 components/ui/table/StatusBadge.tsx create mode 100644 components/ui/table/Table.tsx diff --git a/app/(prowler)/providers/page.tsx b/app/(prowler)/providers/page.tsx index d3cd8c1611..2301d26738 100644 --- a/app/(prowler)/providers/page.tsx +++ b/app/(prowler)/providers/page.tsx @@ -1,20 +1,11 @@ import { Spacer } from "@nextui-org/react"; import React from "react"; -import { CustomTable, Header, ModalWrap } from "@/components"; +import { ColumnsProviders, DataTable, Header, ModalWrap } from "@/components"; +import { getProvider } from "@/lib/actions"; -const visibleColumns: string[] = [ - "account", - "group", - "scan_status", - "last_scan", - "next_scan", - "resources", - "added", - "actions", -]; - -export default function Providers() { +export default async function Providers() { + const providers = await getProvider(); const onSave = async () => { "use server"; // event we want to pass down, ex. console.log("### hello"); @@ -24,23 +15,21 @@ export default function Providers() { <>
- - - -

Modal body content

- - } - actionButtonLabel="Save" - onAction={onSave} - openButtonLabel="Open Modal" - /> +
+ +

Modal body content

+ + } + actionButtonLabel="Save" + onAction={onSave} + openButtonLabel="Add Cloud Accounts" + /> + + +
); } diff --git a/components/index.ts b/components/index.ts index f4acf6e356..faa5b19c57 100644 --- a/components/index.ts +++ b/components/index.ts @@ -1,7 +1,7 @@ export * from "./providers/DateWithTime"; export * from "./providers/ProviderInfo"; export * from "./providers/ScanStatus"; -export * from "./providers/table/columns"; +export * from "./providers/table/ColumnsProviders"; export * from "./providers/table/DataTable"; export * from "./ui/header/Header"; export * from "./ui/modal"; diff --git a/components/providers/DateWithTime.tsx b/components/providers/DateWithTime.tsx index 9895483e31..1e968fae3f 100644 --- a/components/providers/DateWithTime.tsx +++ b/components/providers/DateWithTime.tsx @@ -16,7 +16,7 @@ export const DateWithTime: React.FC = ({ return (
-
+
{formattedDate} {showTime && ( {formattedTime} diff --git a/components/providers/table/ColumnsProviders.tsx b/components/providers/table/ColumnsProviders.tsx new file mode 100644 index 0000000000..c362d95f27 --- /dev/null +++ b/components/providers/table/ColumnsProviders.tsx @@ -0,0 +1,111 @@ +"use client"; + +import { + Button, + Dropdown, + DropdownItem, + DropdownMenu, + DropdownTrigger, +} from "@nextui-org/react"; +import { ColumnDef } from "@tanstack/react-table"; +import { add } from "date-fns"; + +import { VerticalDotsIcon } from "@/components/icons"; +import { StatusBadge } from "@/components/ui/table/StatusBadge"; +import { ProviderProps } from "@/types"; + +import { DateWithTime } from "../DateWithTime"; +import { ProviderInfo } from "../ProviderInfo"; + +export const ColumnsProviders: ColumnDef[] = [ + { + header: "ID", + cell: ({ row }) =>

{row.index + 1}

, + }, + { + accessorKey: "account", + header: "Account", + cell: ({ row }) => { + const provider = row.original; + return ( + + ); + }, + }, + { + accessorKey: "status", + header: "Scan Status", + cell: ({ row }) => { + const provider = row.original; + return ; + }, + }, + { + accessorKey: "lastScan", + header: "Last Scan", + cell: ({ row }) => { + const provider = row.original; + return ; + }, + }, + { + accessorKey: "nextScan", + header: "Next Scan", + cell: ({ row }) => { + const provider = row.original; + const nextDay = add(new Date(provider.attributes.updated_at), { + hours: 24, + }); + return ; + }, + }, + { + accessorKey: "resources", + header: "Resources", + cell: ({ row }) => { + const provider = row.original; + return

{provider.attributes.resources}

; + }, + }, + { + accessorKey: "added", + header: "Added", + cell: ({ row }) => { + const provider = row.original; + return ( + + ); + }, + }, + { + accessorKey: "actions", + header: () =>
Actions
, + id: "actions", + cell: () => { + // const provider = row.original; + return ( +
+ + + + + + Manage + Delete + + +
+ ); + }, + }, +]; diff --git a/components/providers/table/DataTable.tsx b/components/providers/table/DataTable.tsx new file mode 100644 index 0000000000..cfcf74ff2a --- /dev/null +++ b/components/providers/table/DataTable.tsx @@ -0,0 +1,109 @@ +"use client"; + +import { Button } from "@nextui-org/react"; +import { + ColumnDef, + flexRender, + getCoreRowModel, + getPaginationRowModel, + useReactTable, +} from "@tanstack/react-table"; + +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table/Table"; + +interface DataTableProps { + columns: ColumnDef[]; + data: TData[]; +} + +export function DataTable({ + columns, + data, +}: DataTableProps) { + const table = useReactTable({ + data, + columns, + getCoreRowModel: getCoreRowModel(), + getPaginationRowModel: getPaginationRowModel(), + }); + + return ( + <> +
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext(), + )} + + ); + })} + + ))} + + + {table.getRowModel().rows?.length ? ( + table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender( + cell.column.columnDef.cell, + cell.getContext(), + )} + + ))} + + )) + ) : ( + + + No results. + + + )} + +
+
+
+ + +
+ + ); +} diff --git a/components/ui/table/StatusBadge.tsx b/components/ui/table/StatusBadge.tsx new file mode 100644 index 0000000000..a1dea200aa --- /dev/null +++ b/components/ui/table/StatusBadge.tsx @@ -0,0 +1,25 @@ +import { Chip } from "@nextui-org/react"; +import React from "react"; + +type Status = "completed" | "pending" | "cancelled"; + +export const statusColorMap: Record = + { + completed: "success", + pending: "warning", + cancelled: "danger", + }; + +export const StatusBadge = ({ status }: { status: Status }) => { + return ( + + {status} + + ); +}; diff --git a/components/ui/table/Table.tsx b/components/ui/table/Table.tsx new file mode 100644 index 0000000000..cdc1f5cfe6 --- /dev/null +++ b/components/ui/table/Table.tsx @@ -0,0 +1,117 @@ +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +const Table = React.forwardRef< + HTMLTableElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+ + +)); +Table.displayName = "Table"; + +const TableHeader = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)); +TableHeader.displayName = "TableHeader"; + +const TableBody = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)); +TableBody.displayName = "TableBody"; + +const TableFooter = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + tr]:last:border-b-0 dark:bg-slate-800/50", + className, + )} + {...props} + /> +)); +TableFooter.displayName = "TableFooter"; + +const TableRow = React.forwardRef< + HTMLTableRowElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)); +TableRow.displayName = "TableRow"; + +const TableHead = React.forwardRef< + HTMLTableCellElement, + React.ThHTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +TableHead.displayName = "TableHead"; + +const TableCell = React.forwardRef< + HTMLTableCellElement, + React.TdHTMLAttributes +>(({ className, ...props }, ref) => ( + +)); +TableCell.displayName = "TableCell"; + +const TableCaption = React.forwardRef< + HTMLTableCaptionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +TableCaption.displayName = "TableCaption"; + +export { + Table, + TableBody, + TableCaption, + TableCell, + TableFooter, + TableHead, + TableHeader, + TableRow, +}; diff --git a/lib/actions/provider.actions.ts b/lib/actions/provider.actions.ts index 02cb2578e9..12ee153dc1 100644 --- a/lib/actions/provider.actions.ts +++ b/lib/actions/provider.actions.ts @@ -3,8 +3,9 @@ import "server-only"; import { parseStringify } from "../utils"; export const getProvider = async () => { + const key = process.env.LOCAL_SITE_URL; try { - const providers = await fetch("http://localhost:3000/api/providers"); + const providers = await fetch(`${key}/api/providers`); const data = await providers.json(); return parseStringify(data); } catch (error) { From 86a4938b5f145d30993a2bcdb265287fe1b7f8d1 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Tue, 30 Jul 2024 05:48:18 +0200 Subject: [PATCH 068/411] chore: set the default valuef or isCollapse to false --- components/ui/sidebar/SidebarWrap.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/ui/sidebar/SidebarWrap.tsx b/components/ui/sidebar/SidebarWrap.tsx index bca4d50a36..325e284f15 100644 --- a/components/ui/sidebar/SidebarWrap.tsx +++ b/components/ui/sidebar/SidebarWrap.tsx @@ -20,7 +20,7 @@ import { UserAvatar } from "./UserAvatar"; export const SidebarWrap = () => { const pathname = usePathname(); - const [isCollapsed, setIsCollapsed] = useLocalStorage("isCollapsed", true); + const [isCollapsed, setIsCollapsed] = useLocalStorage("isCollapsed", false); const isMobile = useMediaQuery("(max-width: 768px)"); From fd9cff939298c44a5b5084399a239f88f8214d29 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Tue, 30 Jul 2024 06:21:25 +0200 Subject: [PATCH 069/411] chore: Added a helper function getProviderAttributes for cleaner access to provider attributes --- .../providers/table/ColumnsProviders.tsx | 41 +++++++++---------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/components/providers/table/ColumnsProviders.tsx b/components/providers/table/ColumnsProviders.tsx index c362d95f27..3701b68e2a 100644 --- a/components/providers/table/ColumnsProviders.tsx +++ b/components/providers/table/ColumnsProviders.tsx @@ -17,6 +17,10 @@ import { ProviderProps } from "@/types"; import { DateWithTime } from "../DateWithTime"; import { ProviderInfo } from "../ProviderInfo"; +const getProviderAttributes = (row: { original: ProviderProps }) => { + return row.original.attributes; +}; + export const ColumnsProviders: ColumnDef[] = [ { header: "ID", @@ -26,13 +30,14 @@ export const ColumnsProviders: ColumnDef[] = [ accessorKey: "account", header: "Account", cell: ({ row }) => { - const provider = row.original; + const { connection, provider, alias, provider_id } = + getProviderAttributes(row); return ( ); }, @@ -41,24 +46,24 @@ export const ColumnsProviders: ColumnDef[] = [ accessorKey: "status", header: "Scan Status", cell: ({ row }) => { - const provider = row.original; - return ; + const { status } = getProviderAttributes(row); + return ; }, }, { accessorKey: "lastScan", header: "Last Scan", cell: ({ row }) => { - const provider = row.original; - return ; + const { updated_at } = getProviderAttributes(row); + return ; }, }, { accessorKey: "nextScan", header: "Next Scan", cell: ({ row }) => { - const provider = row.original; - const nextDay = add(new Date(provider.attributes.updated_at), { + const { updated_at } = getProviderAttributes(row); + const nextDay = add(new Date(updated_at), { hours: 24, }); return ; @@ -68,21 +73,16 @@ export const ColumnsProviders: ColumnDef[] = [ accessorKey: "resources", header: "Resources", cell: ({ row }) => { - const provider = row.original; - return

{provider.attributes.resources}

; + const { resources } = getProviderAttributes(row); + return

{resources}

; }, }, { accessorKey: "added", header: "Added", cell: ({ row }) => { - const provider = row.original; - return ( - - ); + const { inserted_at } = getProviderAttributes(row); + return ; }, }, { @@ -90,7 +90,6 @@ export const ColumnsProviders: ColumnDef[] = [ header: () =>
Actions
, id: "actions", cell: () => { - // const provider = row.original; return (
From 48f633889a4e830f21177b4f6ec80571bd54a1d5 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Tue, 30 Jul 2024 07:04:54 +0200 Subject: [PATCH 070/411] Providers page table (#20) * fix: add suppressHydrationWarning to resolve console errors * chore: add server-only library * WIP: Mock API for providers and start rendering data * chore: relocate utils folder to proper directory * chore: install shadcn for tables, adding sttings page * refactor: improve sidebar display behavior * chore: add fake data to the dataProviders * chore: remove the old table and rename ProviderInfo component * refactor: improve sidebar display behavior adding a custom hook * feat: the Providers table is rendering real data * chore: set the default valuef or isCollapse to false * chore: Added a helper function getProviderAttributes for cleaner access to provider attributes --- app/(prowler)/layout.tsx | 5 +- app/(prowler)/providers/data.ts | 371 ---------------- app/(prowler)/providers/page.tsx | 49 +-- app/(prowler)/settings/page.tsx | 12 + app/api/providers/route.ts | 10 + components/index.ts | 7 +- components/providers/DateWithTime.tsx | 2 +- .../{AccountInfo.tsx => ProviderInfo.tsx} | 16 +- .../providers/table/ColumnsProviders.tsx | 110 +++++ components/providers/table/DataTable.tsx | 109 +++++ components/ui/sidebar/SidebarWrap.tsx | 29 +- components/ui/table/CustomTable.tsx | 406 ------------------ components/ui/table/StatusBadge.tsx | 25 ++ components/ui/table/Table.tsx | 117 +++++ config/fonts.ts | 5 +- dataProviders.json | 105 +++++ hooks/index.ts | 1 + hooks/useLocalStorage.ts | 46 ++ lib/actions/index.ts | 1 + lib/actions/provider.actions.ts | 14 + lib/index.ts | 1 + lib/utils.ts | 81 ++++ package.json | 10 +- types/components.ts | 27 ++ utils/capitalize.ts | 3 - utils/fetcher.tsx | 1 - 26 files changed, 715 insertions(+), 848 deletions(-) delete mode 100644 app/(prowler)/providers/data.ts create mode 100644 app/(prowler)/settings/page.tsx create mode 100644 app/api/providers/route.ts rename components/providers/{AccountInfo.tsx => ProviderInfo.tsx} (76%) create mode 100644 components/providers/table/ColumnsProviders.tsx create mode 100644 components/providers/table/DataTable.tsx delete mode 100644 components/ui/table/CustomTable.tsx create mode 100644 components/ui/table/StatusBadge.tsx create mode 100644 components/ui/table/Table.tsx create mode 100644 dataProviders.json create mode 100644 hooks/index.ts create mode 100644 hooks/useLocalStorage.ts create mode 100644 lib/actions/index.ts create mode 100644 lib/actions/provider.actions.ts create mode 100644 lib/index.ts create mode 100644 lib/utils.ts delete mode 100644 utils/capitalize.ts delete mode 100644 utils/fetcher.tsx diff --git a/app/(prowler)/layout.tsx b/app/(prowler)/layout.tsx index 9c9b7d2a93..13319c3540 100644 --- a/app/(prowler)/layout.tsx +++ b/app/(prowler)/layout.tsx @@ -1,12 +1,12 @@ import "@/styles/globals.css"; -import clsx from "clsx"; import { Metadata, Viewport } from "next"; import React from "react"; import { SidebarWrap } from "@/components"; import { fontSans } from "@/config/fonts"; import { siteConfig } from "@/config/site"; +import { cn } from "@/lib/utils"; import { Providers } from "../providers"; @@ -37,7 +37,8 @@ export default function RootLayout({ = - { - active: "success", - paused: "danger", - warning: "warning", - }; - -export interface Provider { - id: number; - account: string; - provider_id: number; - connected: boolean; - group: string; - scan_status: Status; - scan_status_time: string; - last_scan: string; - next_scan: string; - provider_img: string; - resources: number; - added: string; - actions?: React.ReactNode; -} - -export const statusOptions = [ - { name: "Active", uid: "active" }, - { name: "Paused", uid: "paused" }, - { name: "Warning", uid: "warning" }, -]; - -// Define columns explicitly -export const columns = [ - { name: "ID", uid: "id", sortable: true, initial_visible: false }, - { name: "ACCOUNT", uid: "account", sortable: false, initial_visible: true }, - { name: "GROUP(s)", uid: "group", sortable: false, initial_visible: false }, - { - name: "SCAN STATUS", - uid: "scan_status", - sortable: false, - initial_visible: false, - }, - { - name: "LAST SCAN", - uid: "last_scan", - sortable: false, - initial_visible: true, - }, - { - name: "NEXT SCAN", - uid: "next_scan", - sortable: false, - initial_visible: true, - }, - { - name: "RESOURCES", - uid: "resources", - sortable: true, - initial_visible: true, - }, - { - name: "ADDED", - uid: "added", - sortable: true, - initial_visible: true, - }, - { name: "ACTIONS", uid: "actions", initial_visible: false }, -]; - -export const providers = [ - { - id: 1, - account: "aws", - provider_id: 740350143844, - connected: true, - group: "Production, Bank 1, Development", - scan_status: "active", - scan_status_time: "5 minutes, 4 seconds", - last_scan: "Jul 17, 2024 4:46pm UTC", - next_scan: "Jul 18, 2024 4:46pm UTC", - provider_img: - "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgdmlld0JveD0iMCAwIDI1NiAyNTYiPjxnIGZpbGw9Im5vbmUiPjxyZWN0IHdpZHRoPSIyNTYiIGhlaWdodD0iMjU2IiBmaWxsPSIjZjRmMmVkIiByeD0iNjAiLz48cGF0aCBmaWxsPSIjMjUyZjNlIiBkPSJNODQuNzQ1IDExMS45NjFjMCAyLjQzNC4yNjMgNC40MDcuNzIzIDUuODU1YTM1IDM1IDAgMCAwIDIuMTA2IDQuNzM3Yy4zMjkuNTI2LjQ2IDEuMDUyLjQ2IDEuNTEzYzAgLjY1OC0uMzk1IDEuMzE2LTEuMjUgMS45NzNsLTQuMTQ1IDIuNzY0Yy0uNTkyLjM5NC0xLjE4NC41OTItMS43MS41OTJjLS42NTggMC0xLjMxNi0uMzI5LTEuOTc0LS45MjFhMjAuNCAyMC40IDAgMCAxLTIuMzY4LTMuMDkyYTUxIDUxIDAgMCAxLTIuMDQtMy44ODJxLTcuNjk3IDkuMDgtMTkuMzQyIDkuMDc5Yy01LjUyNiAwLTkuOTM0LTEuNTc5LTEzLjE1OC00LjczN2MtMy4yMjMtMy4xNTgtNC44NjgtNy4zNjgtNC44NjgtMTIuNjMxYzAtNS41OTMgMS45NzQtMTAuMTMyIDUuOTg3LTEzLjU1M3M5LjM0Mi01LjEzMiAxNi4xMTgtNS4xMzJjMi4yMzcgMCA0LjU0LjE5OCA2Ljk3NC41MjdzNC45MzQuODU1IDcuNTY2IDEuNDQ3di00LjgwM2MwLTUtMS4wNTMtOC40ODctMy4wOTItMTAuNTI2Yy0yLjEwNi0yLjA0LTUuNjU4LTMuMDI2LTEwLjcyNC0zLjAyNmMtMi4zMDMgMC00LjY3MS4yNjMtNy4xMDUuODU1cy00LjgwMyAxLjMxNi03LjEwNiAyLjIzN2ExOSAxOSAwIDAgMS0yLjMwMi44NTVjLS40Ni4xMzItLjc5LjE5OC0xLjA1My4xOThjLS45MiAwLTEuMzgyLS42NTgtMS4zODItMi4wNHYtMy4yMjRjMC0xLjA1Mi4xMzItMS44NDIuNDYxLTIuMzAycy45MjEtLjkyMSAxLjg0Mi0xLjM4MnEzLjQ1NC0xLjc3NiA4LjI5LTIuOTZjMy4yMjMtLjg1NiA2LjY0NC0xLjI1IDEwLjI2My0xLjI1YzcuODI5IDAgMTMuNTUyIDEuNzc2IDE3LjIzNyA1LjMyOGMzLjYxOCAzLjU1MyA1LjQ2IDguOTQ4IDUuNDYgMTYuMTg1djIxLjMxNnptLTI2LjcxIDEwYzIuMTcgMCA0LjQwNy0uMzk1IDYuNzc2LTEuMTg1YzIuMzY4LS43ODkgNC40NzMtMi4yMzcgNi4yNS00LjIxYzEuMDUyLTEuMjUgMS44NDItMi42MzIgMi4yMzYtNC4yMTFzLjY1OC0zLjQ4Ny42NTgtNS43MjN2LTIuNzY0YTU1IDU1IDAgMCAwLTYuMDUyLTEuMTE4YTUwIDUwIDAgMCAwLTYuMTg1LS4zOTVjLTQuNDA4IDAtNy42MzEuODU2LTkuODAyIDIuNjMycy0zLjIyNCA0LjI3Ni0zLjIyNCA3LjU2NmMwIDMuMDkyLjc5IDUuMzk0IDIuNDM0IDYuOTczYzEuNTggMS42NDUgMy44ODIgMi40MzUgNi45MDggMi40MzVtNTIuODI4IDcuMTA1Yy0xLjE4NCAwLTEuOTc0LS4xOTgtMi41LS42NThjLS41MjYtLjM5NS0uOTg3LTEuMzE2LTEuMzgxLTIuNTY2bC0xNS40Ni01MC44NTVjLS4zOTYtMS4zMTYtLjU5My0yLjE3MS0uNTkzLTIuNjMyYzAtMS4wNTIuNTI2LTEuNjQ1IDEuNTc5LTEuNjQ1aDYuNDQ3YzEuMjUgMCAyLjEwNi4xOTggMi41NjYuNjU4Yy41MjYuMzk1LjkyMSAxLjMxNiAxLjMxNiAyLjU2NmwxMS4wNTIgNDMuNTUzbDEwLjI2NC00My41NTNjLjMyOS0xLjMxNi43MjMtMi4xNyAxLjI1LTIuNTY2Yy41MjYtLjM5NCAxLjQ0Ny0uNjU3IDIuNjMxLS42NTdoNS4yNjNjMS4yNSAwIDIuMTA2LjE5NyAyLjYzMi42NTdjLjUyNi4zOTUuOTg3IDEuMzE2IDEuMjUgMi41NjZsMTAuMzk1IDQ0LjA3OWwxMS4zODEtNDQuMDc5Yy4zOTUtMS4zMTYuODU2LTIuMTcgMS4zMTYtMi41NjZjLjUyNi0uMzk0IDEuMzgyLS42NTcgMi41NjYtLjY1N2g2LjExOGMxLjA1MyAwIDEuNjQ1LjUyNiAxLjY0NSAxLjY0NGMwIC4zMy0uMDY2LjY1OC0uMTMyIDEuMDUzYy0uMDY1LjM5NS0uMTk3LjkyLS40NiAxLjY0NWwtMTUuODU1IDUwLjg1NXEtLjU5MyAxLjk3NC0xLjM4MiAyLjU2NmMtLjUyNi4zOTQtMS4zODIuNjU4LTIuNS42NThoLTUuNjU4Yy0xLjI1IDAtMi4xMDUtLjE5OC0yLjYzMS0uNjU4Yy0uNTI3LS40NjEtLjk4Ny0xLjMxNi0xLjI1LTIuNjMybC0xMC4xOTgtNDIuNDM0bC0xMC4xMzEgNDIuMzY4Yy0uMzI5IDEuMzE2LS43MjQgMi4xNzEtMS4yNSAyLjYzMmMtLjUyNy40Ni0xLjQ0OC42NTgtMi42MzIuNjU4em04NC41NCAxLjc3NmMtMy40MjEgMC02Ljg0Mi0uMzk1LTEwLjEzMi0xLjE4NGMtMy4yODktLjc5LTUuODU1LTEuNjQ1LTcuNTY2LTIuNjMyYy0xLjA1Mi0uNTkyLTEuNzc2LTEuMjUtMi4wMzktMS44NDJhNC42NSA0LjY1IDAgMCAxLS4zOTUtMS44NDJ2LTMuMzU1YzAtMS4zODIuNTI2LTIuMDQgMS41MTMtMi4wNHEuNTkzIDAgMS4xODQuMTk4Yy4zOTUuMTMxLjk4Ny4zOTQgMS42NDUuNjU4YTM1LjggMzUuOCAwIDAgMCA3LjIzNyAyLjMwMmEzOS41IDM5LjUgMCAwIDAgNy44MjkuNzljNC4xNDUgMCA3LjM2OC0uNzI0IDkuNjA1LTIuMTcxYzIuMjM3LTEuNDQ4IDMuNDIxLTMuNTUzIDMuNDIxLTYuMjVjMC0xLjg0Mi0uNTkyLTMuMzU2LTEuNzc2LTQuNjA2cy0zLjQyMS0yLjM2OC02LjY0NS0zLjQyMWwtOS41MzktMi45NmMtNC44MDMtMS41MTMtOC4zNTYtMy43NS0xMC41MjctNi43MWMtMi4xNzEtMi44OTUtMy4yODktNi4xMi0zLjI4OS05LjU0cTAtNC4xNDQgMS43NzYtNy4zMDNjMS4xODQtMi4xMDUgMi43NjMtMy45NDcgNC43MzctNS4zOTRjMS45NzQtMS41MTQgNC4yMTEtMi42MzIgNi44NDItMy40MjJjMi42MzItLjc5IDUuMzk1LTEuMTE4IDguMjktMS4xMThjMS40NDcgMCAyLjk2LjA2NiA0LjQwOC4yNjNjMS41MTMuMTk3IDIuODk0LjQ2IDQuMjc2LjcyNGMxLjMxNi4zMjkgMi41NjYuNjU4IDMuNzUgMS4wNTNxMS43NzYuNTkxIDIuNzYzIDEuMTg0Yy45MjEuNTI2IDEuNTc5IDEuMDUyIDEuOTc0IDEuNjQ0cS41OTIuNzkuNTkyIDIuMTcydjMuMDkyYzAgMS4zODEtLjUyNiAyLjEwNS0xLjUxMyAyLjEwNWMtLjUyNyAwLTEuMzgyLS4yNjMtMi41LS43OXEtNS42MjUtMi41NjUtMTIuNjMyLTIuNTY1Yy0zLjc1IDAtNi43MS41OTItOC43NSAxLjg0MnMtMy4wOTIgMy4xNTgtMy4wOTIgNS44NTVjMCAxLjg0Mi42NTggMy40MjEgMS45NzQgNC42NzFzMy43NSAyLjUgNy4yMzcgMy42MThsOS4zNDIgMi45NmM0LjczNiAxLjUxNCA4LjE1OCAzLjYxOSAxMC4xOTcgNi4zMTdzMy4wMjYgNS43ODkgMy4wMjYgOS4yMWMwIDIuODI5LS41OTIgNS4zOTUtMS43MSA3LjYzMmMtMS4xODQgMi4yMzctMi43NjMgNC4yMS00LjgwMyA1Ljc4OWMtMi4wMzkgMS42NDUtNC40NzQgMi44MjktNy4zMDIgMy42ODVjLTIuOTYxLjkyMS02LjA1MyAxLjM4MS05LjQwOCAxLjM4MSIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIwNy44MzcgMTYyLjgxNmMtMjEuNjQ1IDE1Ljk4Ny01My4wOTIgMjQuNDc0LTgwLjEzMiAyNC40NzRjLTM3Ljg5NCAwLTcyLjA0LTE0LjAxNC05Ny44MjktMzcuMzAzYy0yLjA0LTEuODQyLS4xOTctNC4zNDIgMi4yMzctMi44OTVjMjcuODk1IDE2LjE4NCA2Mi4zMDMgMjUuOTg3IDk3Ljg5NSAyNS45ODdjMjQuMDEzIDAgNTAuMzk1LTUgNzQuNjcxLTE1LjI2M2MzLjYxOC0xLjY0NSA2LjcxIDIuMzY4IDMuMTU4IDUiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIxNi44NSAxNTIuNTUzYy0yLjc2My0zLjU1My0xOC4yODktMS43MTEtMjUuMzI5LS44NTZjLTIuMTA1LjI2NC0yLjQzNC0xLjU3OS0uNTI2LTIuOTZjMTIuMzY4LTguNjg0IDMyLjY5Ny02LjE4NCAzNS4wNjYtMy4yOWMyLjM2OCAyLjk2MS0uNjU4IDIzLjI5LTEyLjIzNyAzMy4wMjdjLTEuNzc3IDEuNTEzLTMuNDg3LjcyMy0yLjY5OC0xLjI1YzIuNjMyLTYuNTEzIDguNDg3LTIxLjE4NSA1LjcyNC0yNC42NzEiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=", - resources: 132, - added: "Apr 1, 2024", - }, - { - id: 2, - account: "gcp", - provider_id: 740350143845, - connected: false, - group: "Development", - scan_status: "paused", - scan_status_time: "10 minutes, 15 seconds", - last_scan: "Jul 17, 2024 3:46pm UTC", - next_scan: "Jul 18, 2024 3:46pm UTC", - provider_img: - "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgdmlld0JveD0iMCAwIDI1NiAyNTYiPjxnIGZpbGw9Im5vbmUiPjxyZWN0IHdpZHRoPSIyNTYiIGhlaWdodD0iMjU2IiBmaWxsPSIjZjRmMmVkIiByeD0iNjAiLz48cGF0aCBmaWxsPSIjMjUyZjNlIiBkPSJNODQuNzQ1IDExMS45NjFjMCAyLjQzNC4yNjMgNC40MDcuNzIzIDUuODU1YTM1IDM1IDAgMCAwIDIuMTA2IDQuNzM3Yy4zMjkuNTI2LjQ2IDEuMDUyLjQ2IDEuNTEzYzAgLjY1OC0uMzk1IDEuMzE2LTEuMjUgMS45NzNsLTQuMTQ1IDIuNzY0Yy0uNTkyLjM5NC0xLjE4NC41OTItMS43MS41OTJjLS42NTggMC0xLjMxNi0uMzI5LTEuOTc0LS45MjFhMjAuNCAyMC40IDAgMCAxLTIuMzY4LTMuMDkyYTUxIDUxIDAgMCAxLTIuMDQtMy44ODJxLTcuNjk3IDkuMDgtMTkuMzQyIDkuMDc5Yy01LjUyNiAwLTkuOTM0LTEuNTc5LTEzLjE1OC00LjczN2MtMy4yMjMtMy4xNTgtNC44NjgtNy4zNjgtNC44NjgtMTIuNjMxYzAtNS41OTMgMS45NzQtMTAuMTMyIDUuOTg3LTEzLjU1M3M5LjM0Mi01LjEzMiAxNi4xMTgtNS4xMzJjMi4yMzcgMCA0LjU0LjE5OCA2Ljk3NC41MjdzNC45MzQuODU1IDcuNTY2IDEuNDQ3di00LjgwM2MwLTUtMS4wNTMtOC40ODctMy4wOTItMTAuNTI2Yy0yLjEwNi0yLjA0LTUuNjU4LTMuMDI2LTEwLjcyNC0zLjAyNmMtMi4zMDMgMC00LjY3MS4yNjMtNy4xMDUuODU1cy00LjgwMyAxLjMxNi03LjEwNiAyLjIzN2ExOSAxOSAwIDAgMS0yLjMwMi44NTVjLS40Ni4xMzItLjc5LjE5OC0xLjA1My4xOThjLS45MiAwLTEuMzgyLS42NTgtMS4zODItMi4wNHYtMy4yMjRjMC0xLjA1Mi4xMzItMS44NDIuNDYxLTIuMzAycy45MjEtLjkyMSAxLjg0Mi0xLjM4MnEzLjQ1NC0xLjc3NiA4LjI5LTIuOTZjMy4yMjMtLjg1NiA2LjY0NC0xLjI1IDEwLjI2My0xLjI1YzcuODI5IDAgMTMuNTUyIDEuNzc2IDE3LjIzNyA1LjMyOGMzLjYxOCAzLjU1MyA1LjQ2IDguOTQ4IDUuNDYgMTYuMTg1djIxLjMxNnptLTI2LjcxIDEwYzIuMTcgMCA0LjQwNy0uMzk1IDYuNzc2LTEuMTg1YzIuMzY4LS43ODkgNC40NzMtMi4yMzcgNi4yNS00LjIxYzEuMDUyLTEuMjUgMS44NDItMi42MzIgMi4yMzYtNC4yMTFzLjY1OC0zLjQ4Ny42NTgtNS43MjN2LTIuNzY0YTU1IDU1IDAgMCAwLTYuMDUyLTEuMTE4YTUwIDUwIDAgMCAwLTYuMTg1LS4zOTVjLTQuNDA4IDAtNy42MzEuODU2LTkuODAyIDIuNjMycy0zLjIyNCA0LjI3Ni0zLjIyNCA3LjU2NmMwIDMuMDkyLjc5IDUuMzk0IDIuNDM0IDYuOTczYzEuNTggMS42NDUgMy44ODIgMi40MzUgNi45MDggMi40MzVtNTIuODI4IDcuMTA1Yy0xLjE4NCAwLTEuOTc0LS4xOTgtMi41LS42NThjLS41MjYtLjM5NS0uOTg3LTEuMzE2LTEuMzgxLTIuNTY2bC0xNS40Ni01MC44NTVjLS4zOTYtMS4zMTYtLjU5My0yLjE3MS0uNTkzLTIuNjMyYzAtMS4wNTIuNTI2LTEuNjQ1IDEuNTc5LTEuNjQ1aDYuNDQ3YzEuMjUgMCAyLjEwNi4xOTggMi41NjYuNjU4Yy41MjYuMzk1LjkyMSAxLjMxNiAxLjMxNiAyLjU2NmwxMS4wNTIgNDMuNTUzbDEwLjI2NC00My41NTNjLjMyOS0xLjMxNi43MjMtMi4xNyAxLjI1LTIuNTY2Yy41MjYtLjM5NCAxLjQ0Ny0uNjU3IDIuNjMxLS42NTdoNS4yNjNjMS4yNSAwIDIuMTA2LjE5NyAyLjYzMi42NTdjLjUyNi4zOTUuOTg3IDEuMzE2IDEuMjUgMi41NjZsMTAuMzk1IDQ0LjA3OWwxMS4zODEtNDQuMDc5Yy4zOTUtMS4zMTYuODU2LTIuMTcgMS4zMTYtMi41NjZjLjUyNi0uMzk0IDEuMzgyLS42NTcgMi41NjYtLjY1N2g2LjExOGMxLjA1MyAwIDEuNjQ1LjUyNiAxLjY0NSAxLjY0NGMwIC4zMy0uMDY2LjY1OC0uMTMyIDEuMDUzYy0uMDY1LjM5NS0uMTk3LjkyLS40NiAxLjY0NWwtMTUuODU1IDUwLjg1NXEtLjU5MyAxLjk3NC0xLjM4MiAyLjU2NmMtLjUyNi4zOTQtMS4zODIuNjU4LTIuNS42NThoLTUuNjU4Yy0xLjI1IDAtMi4xMDUtLjE5OC0yLjYzMS0uNjU4Yy0uNTI3LS40NjEtLjk4Ny0xLjMxNi0xLjI1LTIuNjMybC0xMC4xOTgtNDIuNDM0bC0xMC4xMzEgNDIuMzY4Yy0uMzI5IDEuMzE2LS43MjQgMi4xNzEtMS4yNSAyLjYzMmMtLjUyNy40Ni0xLjQ0OC42NTgtMi42MzIuNjU4em04NC41NCAxLjc3NmMtMy40MjEgMC02Ljg0Mi0uMzk1LTEwLjEzMi0xLjE4NGMtMy4yODktLjc5LTUuODU1LTEuNjQ1LTcuNTY2LTIuNjMyYy0xLjA1Mi0uNTkyLTEuNzc2LTEuMjUtMi4wMzktMS44NDJhNC42NSA0LjY1IDAgMCAxLS4zOTUtMS44NDJ2LTMuMzU1YzAtMS4zODIuNTI2LTIuMDQgMS41MTMtMi4wNHEuNTkzIDAgMS4xODQuMTk4Yy4zOTUuMTMxLjk4Ny4zOTQgMS42NDUuNjU4YTM1LjggMzUuOCAwIDAgMCA3LjIzNyAyLjMwMmEzOS41IDM5LjUgMCAwIDAgNy44MjkuNzljNC4xNDUgMCA3LjM2OC0uNzI0IDkuNjA1LTIuMTcxYzIuMjM3LTEuNDQ4IDMuNDIxLTMuNTUzIDMuNDIxLTYuMjVjMC0xLjg0Mi0uNTkyLTMuMzU2LTEuNzc2LTQuNjA2cy0zLjQyMS0yLjM2OC02LjY0NS0zLjQyMWwtOS41MzktMi45NmMtNC44MDMtMS41MTMtOC4zNTYtMy43NS0xMC41MjctNi43MWMtMi4xNzEtMi44OTUtMy4yODktNi4xMi0zLjI4OS05LjU0cTAtNC4xNDQgMS43NzYtNy4zMDNjMS4xODQtMi4xMDUgMi43NjMtMy45NDcgNC43MzctNS4zOTRjMS45NzQtMS41MTQgNC4yMTEtMi42MzIgNi44NDItMy40MjJjMi42MzItLjc5IDUuMzk1LTEuMTE4IDguMjktMS4xMThjMS40NDcgMCAyLjk2LjA2NiA0LjQwOC4yNjNjMS41MTMuMTk3IDIuODk0LjQ2IDQuMjc2LjcyNGMxLjMxNi4zMjkgMi41NjYuNjU4IDMuNzUgMS4wNTNxMS43NzYuNTkxIDIuNzYzIDEuMTg0Yy45MjEuNTI2IDEuNTc5IDEuMDUyIDEuOTc0IDEuNjQ0cS41OTIuNzkuNTkyIDIuMTcydjMuMDkyYzAgMS4zODEtLjUyNiAyLjEwNS0xLjUxMyAyLjEwNWMtLjUyNyAwLTEuMzgyLS4yNjMtMi41LS43OXEtNS42MjUtMi41NjUtMTIuNjMyLTIuNTY1Yy0zLjc1IDAtNi43MS41OTItOC43NSAxLjg0MnMtMy4wOTIgMy4xNTgtMy4wOTIgNS44NTVjMCAxLjg0Mi42NTggMy40MjEgMS45NzQgNC42NzFzMy43NSAyLjUgNy4yMzcgMy42MThsOS4zNDIgMi45NmM0LjczNiAxLjUxNCA4LjE1OCAzLjYxOSAxMC4xOTcgNi4zMTdzMy4wMjYgNS43ODkgMy4wMjYgOS4yMWMwIDIuODI5LS41OTIgNS4zOTUtMS43MSA3LjYzMmMtMS4xODQgMi4yMzctMi43NjMgNC4yMS00LjgwMyA1Ljc4OWMtMi4wMzkgMS42NDUtNC40NzQgMi44MjktNy4zMDIgMy42ODVjLTIuOTYxLjkyMS02LjA1MyAxLjM4MS05LjQwOCAxLjM4MSIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIwNy44MzcgMTYyLjgxNmMtMjEuNjQ1IDE1Ljk4Ny01My4wOTIgMjQuNDc0LTgwLjEzMiAyNC40NzRjLTM3Ljg5NCAwLTcyLjA0LTE0LjAxNC05Ny44MjktMzcuMzAzYy0yLjA0LTEuODQyLS4xOTctNC4zNDIgMi4yMzctMi44OTVjMjcuODk1IDE2LjE4NCA2Mi4zMDMgMjUuOTg3IDk3Ljg5NSAyNS45ODdjMjQuMDEzIDAgNTAuMzk1LTUgNzQuNjcxLTE1LjI2M2MzLjYxOC0xLjY0NSA2LjcxIDIuMzY4IDMuMTU4IDUiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIxNi44NSAxNTIuNTUzYy0yLjc2My0zLjU1My0xOC4yODktMS43MTEtMjUuMzI5LS44NTZjLTIuMTA1LjI2NC0yLjQzNC0xLjU3OS0uNTI2LTIuOTZjMTIuMzY4LTguNjg0IDMyLjY5Ny02LjE4NCAzNS4wNjYtMy4yOWMyLjM2OCAyLjk2MS0uNjU4IDIzLjI5LTEyLjIzNyAzMy4wMjdjLTEuNzc3IDEuNTEzLTMuNDg3LjcyMy0yLjY5OC0xLjI1YzIuNjMyLTYuNTEzIDguNDg3LTIxLjE4NSA1LjcyNC0yNC42NzEiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=", - resources: 98, - added: "Mar 15, 2024", - }, - { - id: 3, - account: "azure", - provider_id: 740350143846, - connected: true, - group: "Production, Development", - scan_status: "active", - scan_status_time: "2 minutes, 30 seconds", - last_scan: "Jul 17, 2024 2:46pm UTC", - next_scan: "Jul 18, 2024 2:46pm UTC", - provider_img: - "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgdmlld0JveD0iMCAwIDI1NiAyNTYiPjxnIGZpbGw9Im5vbmUiPjxyZWN0IHdpZHRoPSIyNTYiIGhlaWdodD0iMjU2IiBmaWxsPSIjZjRmMmVkIiByeD0iNjAiLz48cGF0aCBmaWxsPSIjMjUyZjNlIiBkPSJNODQuNzQ1IDExMS45NjFjMCAyLjQzNC4yNjMgNC40MDcuNzIzIDUuODU1YTM1IDM1IDAgMCAwIDIuMTA2IDQuNzM3Yy4zMjkuNTI2LjQ2IDEuMDUyLjQ2IDEuNTEzYzAgLjY1OC0uMzk1IDEuMzE2LTEuMjUgMS45NzNsLTQuMTQ1IDIuNzY0Yy0uNTkyLjM5NC0xLjE4NC41OTItMS43MS41OTJjLS42NTggMC0xLjMxNi0uMzI5LTEuOTc0LS45MjFhMjAuNCAyMC40IDAgMCAxLTIuMzY4LTMuMDkyYTUxIDUxIDAgMCAxLTIuMDQtMy44ODJxLTcuNjk3IDkuMDgtMTkuMzQyIDkuMDc5Yy01LjUyNiAwLTkuOTM0LTEuNTc5LTEzLjE1OC00LjczN2MtMy4yMjMtMy4xNTgtNC44NjgtNy4zNjgtNC44NjgtMTIuNjMxYzAtNS41OTMgMS45NzQtMTAuMTMyIDUuOTg3LTEzLjU1M3M5LjM0Mi01LjEzMiAxNi4xMTgtNS4xMzJjMi4yMzcgMCA0LjU0LjE5OCA2Ljk3NC41MjdzNC45MzQuODU1IDcuNTY2IDEuNDQ3di00LjgwM2MwLTUtMS4wNTMtOC40ODctMy4wOTItMTAuNTI2Yy0yLjEwNi0yLjA0LTUuNjU4LTMuMDI2LTEwLjcyNC0zLjAyNmMtMi4zMDMgMC00LjY3MS4yNjMtNy4xMDUuODU1cy00LjgwMyAxLjMxNi03LjEwNiAyLjIzN2ExOSAxOSAwIDAgMS0yLjMwMi44NTVjLS40Ni4xMzItLjc5LjE5OC0xLjA1My4xOThjLS45MiAwLTEuMzgyLS42NTgtMS4zODItMi4wNHYtMy4yMjRjMC0xLjA1Mi4xMzItMS44NDIuNDYxLTIuMzAycy45MjEtLjkyMSAxLjg0Mi0xLjM4MnEzLjQ1NC0xLjc3NiA4LjI5LTIuOTZjMy4yMjMtLjg1NiA2LjY0NC0xLjI1IDEwLjI2My0xLjI1YzcuODI5IDAgMTMuNTUyIDEuNzc2IDE3LjIzNyA1LjMyOGMzLjYxOCAzLjU1MyA1LjQ2IDguOTQ4IDUuNDYgMTYuMTg1djIxLjMxNnptLTI2LjcxIDEwYzIuMTcgMCA0LjQwNy0uMzk1IDYuNzc2LTEuMTg1YzIuMzY4LS43ODkgNC40NzMtMi4yMzcgNi4yNS00LjIxYzEuMDUyLTEuMjUgMS44NDItMi42MzIgMi4yMzYtNC4yMTFzLjY1OC0zLjQ4Ny42NTgtNS43MjN2LTIuNzY0YTU1IDU1IDAgMCAwLTYuMDUyLTEuMTE4YTUwIDUwIDAgMCAwLTYuMTg1LS4zOTVjLTQuNDA4IDAtNy42MzEuODU2LTkuODAyIDIuNjMycy0zLjIyNCA0LjI3Ni0zLjIyNCA3LjU2NmMwIDMuMDkyLjc5IDUuMzk0IDIuNDM0IDYuOTczYzEuNTggMS42NDUgMy44ODIgMi40MzUgNi45MDggMi40MzVtNTIuODI4IDcuMTA1Yy0xLjE4NCAwLTEuOTc0LS4xOTgtMi41LS42NThjLS41MjYtLjM5NS0uOTg3LTEuMzE2LTEuMzgxLTIuNTY2bC0xNS40Ni01MC44NTVjLS4zOTYtMS4zMTYtLjU5My0yLjE3MS0uNTkzLTIuNjMyYzAtMS4wNTIuNTI2LTEuNjQ1IDEuNTc5LTEuNjQ1aDYuNDQ3YzEuMjUgMCAyLjEwNi4xOTggMi41NjYuNjU4Yy41MjYuMzk1LjkyMSAxLjMxNiAxLjMxNiAyLjU2NmwxMS4wNTIgNDMuNTUzbDEwLjI2NC00My41NTNjLjMyOS0xLjMxNi43MjMtMi4xNyAxLjI1LTIuNTY2Yy41MjYtLjM5NCAxLjQ0Ny0uNjU3IDIuNjMxLS42NTdoNS4yNjNjMS4yNSAwIDIuMTA2LjE5NyAyLjYzMi42NTdjLjUyNi4zOTUuOTg3IDEuMzE2IDEuMjUgMi41NjZsMTAuMzk1IDQ0LjA3OWwxMS4zODEtNDQuMDc5Yy4zOTUtMS4zMTYuODU2LTIuMTcgMS4zMTYtMi41NjZjLjUyNi0uMzk0IDEuMzgyLS42NTcgMi41NjYtLjY1N2g2LjExOGMxLjA1MyAwIDEuNjQ1LjUyNiAxLjY0NSAxLjY0NGMwIC4zMy0uMDY2LjY1OC0uMTMyIDEuMDUzYy0uMDY1LjM5NS0uMTk3LjkyLS40NiAxLjY0NWwtMTUuODU1IDUwLjg1NXEtLjU5MyAxLjk3NC0xLjM4MiAyLjU2NmMtLjUyNi4zOTQtMS4zODIuNjU4LTIuNS42NThoLTUuNjU4Yy0xLjI1IDAtMi4xMDUtLjE5OC0yLjYzMS0uNjU4Yy0uNTI3LS40NjEtLjk4Ny0xLjMxNi0xLjI1LTIuNjMybC0xMC4xOTgtNDIuNDM0bC0xMC4xMzEgNDIuMzY4Yy0uMzI5IDEuMzE2LS43MjQgMi4xNzEtMS4yNSAyLjYzMmMtLjUyNy40Ni0xLjQ0OC42NTgtMi42MzIuNjU4em04NC41NCAxLjc3NmMtMy40MjEgMC02Ljg0Mi0uMzk1LTEwLjEzMi0xLjE4NGMtMy4yODktLjc5LTUuODU1LTEuNjQ1LTcuNTY2LTIuNjMyYy0xLjA1Mi0uNTkyLTEuNzc2LTEuMjUtMi4wMzktMS44NDJhNC42NSA0LjY1IDAgMCAxLS4zOTUtMS44NDJ2LTMuMzU1YzAtMS4zODIuNTI2LTIuMDQgMS41MTMtMi4wNHEuNTkzIDAgMS4xODQuMTk4Yy4zOTUuMTMxLjk4Ny4zOTQgMS42NDUuNjU4YTM1LjggMzUuOCAwIDAgMCA3LjIzNyAyLjMwMmEzOS41IDM5LjUgMCAwIDAgNy44MjkuNzljNC4xNDUgMCA3LjM2OC0uNzI0IDkuNjA1LTIuMTcxYzIuMjM3LTEuNDQ4IDMuNDIxLTMuNTUzIDMuNDIxLTYuMjVjMC0xLjg0Mi0uNTkyLTMuMzU2LTEuNzc2LTQuNjA2cy0zLjQyMS0yLjM2OC02LjY0NS0zLjQyMWwtOS41MzktMi45NmMtNC44MDMtMS41MTMtOC4zNTYtMy43NS0xMC41MjctNi43MWMtMi4xNzEtMi44OTUtMy4yODktNi4xMi0zLjI4OS05LjU0cTAtNC4xNDQgMS43NzYtNy4zMDNjMS4xODQtMi4xMDUgMi43NjMtMy45NDcgNC43MzctNS4zOTRjMS45NzQtMS41MTQgNC4yMTEtMi42MzIgNi44NDItMy40MjJjMi42MzItLjc5IDUuMzk1LTEuMTE4IDguMjktMS4xMThjMS40NDcgMCAyLjk2LjA2NiA0LjQwOC4yNjNjMS41MTMuMTk3IDIuODk0LjQ2IDQuMjc2LjcyNGMxLjMxNi4zMjkgMi41NjYuNjU4IDMuNzUgMS4wNTNxMS43NzYuNTkxIDIuNzYzIDEuMTg0Yy45MjEuNTI2IDEuNTc5IDEuMDUyIDEuOTc0IDEuNjQ0cS41OTIuNzkuNTkyIDIuMTcydjMuMDkyYzAgMS4zODEtLjUyNiAyLjEwNS0xLjUxMyAyLjEwNWMtLjUyNyAwLTEuMzgyLS4yNjMtMi41LS43OXEtNS42MjUtMi41NjUtMTIuNjMyLTIuNTY1Yy0zLjc1IDAtNi43MS41OTItOC43NSAxLjg0MnMtMy4wOTIgMy4xNTgtMy4wOTIgNS44NTVjMCAxLjg0Mi42NTggMy40MjEgMS45NzQgNC42NzFzMy43NSAyLjUgNy4yMzcgMy42MThsOS4zNDIgMi45NmM0LjczNiAxLjUxNCA4LjE1OCAzLjYxOSAxMC4xOTcgNi4zMTdzMy4wMjYgNS43ODkgMy4wMjYgOS4yMWMwIDIuODI5LS41OTIgNS4zOTUtMS43MSA3LjYzMmMtMS4xODQgMi4yMzctMi43NjMgNC4yMS00LjgwMyA1Ljc4OWMtMi4wMzkgMS42NDUtNC40NzQgMi44MjktNy4zMDIgMy42ODVjLTIuOTYxLjkyMS02LjA1MyAxLjM4MS05LjQwOCAxLjM4MSIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIwNy44MzcgMTYyLjgxNmMtMjEuNjQ1IDE1Ljk4Ny01My4wOTIgMjQuNDc0LTgwLjEzMiAyNC40NzRjLTM3Ljg5NCAwLTcyLjA0LTE0LjAxNC05Ny44MjktMzcuMzAzYy0yLjA0LTEuODQyLS4xOTctNC4zNDIgMi4yMzctMi44OTVjMjcuODk1IDE2LjE4NCA2Mi4zMDMgMjUuOTg3IDk3Ljg5NSAyNS45ODdjMjQuMDEzIDAgNTAuMzk1LTUgNzQuNjcxLTE1LjI2M2MzLjYxOC0xLjY0NSA2LjcxIDIuMzY4IDMuMTU4IDUiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIxNi44NSAxNTIuNTUzYy0yLjc2My0zLjU1My0xOC4yODktMS43MTEtMjUuMzI5LS44NTZjLTIuMTA1LjI2NC0yLjQzNC0xLjU3OS0uNTI2LTIuOTZjMTIuMzY4LTguNjg0IDMyLjY5Ny02LjE4NCAzNS4wNjYtMy4yOWMyLjM2OCAyLjk2MS0uNjU4IDIzLjI5LTEyLjIzNyAzMy4wMjdjLTEuNzc3IDEuNTEzLTMuNDg3LjcyMy0yLjY5OC0xLjI1YzIuNjMyLTYuNTEzIDguNDg3LTIxLjE4NSA1LjcyNC0yNC42NzEiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=", - resources: 210, - added: "Feb 20, 2024", - }, - { - id: 4, - account: "digitalocean", - provider_id: 740350143847, - connected: true, - group: "Marketing, Development", - scan_status: "completed", - scan_status_time: "15 minutes, 10 seconds", - last_scan: "Jul 17, 2024 1:46pm UTC", - next_scan: "Jul 18, 2024 1:46pm UTC", - provider_img: - "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgdmlld0JveD0iMCAwIDI1NiAyNTYiPjxnIGZpbGw9Im5vbmUiPjxyZWN0IHdpZHRoPSIyNTYiIGhlaWdodD0iMjU2IiBmaWxsPSIjZjRmMmVkIiByeD0iNjAiLz48cGF0aCBmaWxsPSIjMjUyZjNlIiBkPSJNODQuNzQ1IDExMS45NjFjMCAyLjQzNC4yNjMgNC40MDcuNzIzIDUuODU1YTM1IDM1IDAgMCAwIDIuMTA2IDQuNzM3Yy4zMjkuNTI2LjQ2IDEuMDUyLjQ2IDEuNTEzYzAgLjY1OC0uMzk1IDEuMzE2LTEuMjUgMS45NzNsLTQuMTQ1IDIuNzY0Yy0uNTkyLjM5NC0xLjE4NC41OTItMS43MS41OTJjLS42NTggMC0xLjMxNi0uMzI5LTEuOTc0LS45MjFhMjAuNCAyMC40IDAgMCAxLTIuMzY4LTMuMDkyYTUxIDUxIDAgMCAxLTIuMDQtMy44ODJxLTcuNjk3IDkuMDgtMTkuMzQyIDkuMDc5Yy01LjUyNiAwLTkuOTM0LTEuNTc5LTEzLjE1OC00LjczN2MtMy4yMjMtMy4xNTgtNC44NjgtNy4zNjgtNC44NjgtMTIuNjMxYzAtNS41OTMgMS45NzQtMTAuMTMyIDUuOTg3LTEzLjU1M3M5LjM0Mi01LjEzMiAxNi4xMTgtNS4xMzJjMi4yMzcgMCA0LjU0LjE5OCA2Ljk3NC41MjdzNC45MzQuODU1IDcuNTY2IDEuNDQ3di00LjgwM2MwLTUtMS4wNTMtOC40ODctMy4wOTItMTAuNTI2Yy0yLjEwNi0yLjA0LTUuNjU4LTMuMDI2LTEwLjcyNC0zLjAyNmMtMi4zMDMgMC00LjY3MS4yNjMtNy4xMDUuODU1cy00LjgwMyAxLjMxNi03LjEwNiAyLjIzN2ExOSAxOSAwIDAgMS0yLjMwMi44NTVjLS40Ni4xMzItLjc5LjE5OC0xLjA1My4xOThjLS45MiAwLTEuMzgyLS42NTgtMS4zODItMi4wNHYtMy4yMjRjMC0xLjA1Mi4xMzItMS44NDIuNDYxLTIuMzAycy45MjEtLjkyMSAxLjg0Mi0xLjM4MnEzLjQ1NC0xLjc3NiA4LjI5LTIuOTZjMy4yMjMtLjg1NiA2LjY0NC0xLjI1IDEwLjI2My0xLjI1YzcuODI5IDAgMTMuNTUyIDEuNzc2IDE3LjIzNyA1LjMyOGMzLjYxOCAzLjU1MyA1LjQ2IDguOTQ4IDUuNDYgMTYuMTg1djIxLjMxNnptLTI2LjcxIDEwYzIuMTcgMCA0LjQwNy0uMzk1IDYuNzc2LTEuMTg1YzIuMzY4LS43ODkgNC40NzMtMi4yMzcgNi4yNS00LjIxYzEuMDUyLTEuMjUgMS44NDItMi42MzIgMi4yMzYtNC4yMTFzLjY1OC0zLjQ4Ny42NTgtNS43MjN2LTIuNzY0YTU1IDU1IDAgMCAwLTYuMDUyLTEuMTE4YTUwIDUwIDAgMCAwLTYuMTg1LS4zOTVjLTQuNDA4IDAtNy42MzEuODU2LTkuODAyIDIuNjMycy0zLjIyNCA0LjI3Ni0zLjIyNCA3LjU2NmMwIDMuMDkyLjc5IDUuMzk0IDIuNDM0IDYuOTczYzEuNTggMS42NDUgMy44ODIgMi40MzUgNi45MDggMi40MzVtNTIuODI4IDcuMTA1Yy0xLjE4NCAwLTEuOTc0LS4xOTgtMi41LS42NThjLS41MjYtLjM5NS0uOTg3LTEuMzE2LTEuMzgxLTIuNTY2bC0xNS40Ni01MC44NTVjLS4zOTYtMS4zMTYtLjU5My0yLjE3MS0uNTkzLTIuNjMyYzAtMS4wNTIuNTI2LTEuNjQ1IDEuNTc5LTEuNjQ1aDYuNDQ3YzEuMjUgMCAyLjEwNi4xOTggMi41NjYuNjU4Yy41MjYuMzk1LjkyMSAxLjMxNiAxLjMxNiAyLjU2NmwxMS4wNTIgNDMuNTUzbDEwLjI2NC00My41NTNjLjMyOS0xLjMxNi43MjMtMi4xNyAxLjI1LTIuNTY2Yy41MjYtLjM5NCAxLjQ0Ny0uNjU3IDIuNjMxLS42NTdoNS4yNjNjMS4yNSAwIDIuMTA2LjE5NyAyLjYzMi42NTdjLjUyNi4zOTUuOTg3IDEuMzE2IDEuMjUgMi41NjZsMTAuMzk1IDQ0LjA3OWwxMS4zODEtNDQuMDc5Yy4zOTUtMS4zMTYuODU2LTIuMTcgMS4zMTYtMi41NjZjLjUyNi0uMzk0IDEuMzgyLS42NTcgMi41NjYtLjY1N2g2LjExOGMxLjA1MyAwIDEuNjQ1LjUyNiAxLjY0NSAxLjY0NGMwIC4zMy0uMDY2LjY1OC0uMTMyIDEuMDUzYy0uMDY1LjM5NS0uMTk3LjkyLS40NiAxLjY0NWwtMTUuODU1IDUwLjg1NXEtLjU5MyAxLjk3NC0xLjM4MiAyLjU2NmMtLjUyNi4zOTQtMS4zODIuNjU4LTIuNS42NThoLTUuNjU4Yy0xLjI1IDAtMi4xMDUtLjE5OC0yLjYzMS0uNjU4Yy0uNTI3LS40NjEtLjk4Ny0xLjMxNi0xLjI1LTIuNjMybC0xMC4xOTgtNDIuNDM0bC0xMC4xMzEgNDIuMzY4Yy0uMzI5IDEuMzE2LS43MjQgMi4xNzEtMS4yNSAyLjYzMmMtLjUyNy40Ni0xLjQ0OC42NTgtMi42MzIuNjU4em04NC41NCAxLjc3NmMtMy40MjEgMC02Ljg0Mi0uMzk1LTEwLjEzMi0xLjE4NGMtMy4yODktLjc5LTUuODU1LTEuNjQ1LTcuNTY2LTIuNjMyYy0xLjA1Mi0uNTkyLTEuNzc2LTEuMjUtMi4wMzktMS44NDJhNC42NSA0LjY1IDAgMCAxLS4zOTUtMS44NDJ2LTMuMzU1YzAtMS4zODIuNTI2LTIuMDQgMS41MTMtMi4wNHEuNTkzIDAgMS4xODQuMTk4Yy4zOTUuMTMxLjk4Ny4zOTQgMS42NDUuNjU4YTM1LjggMzUuOCAwIDAgMCA3LjIzNyAyLjMwMmEzOS41IDM5LjUgMCAwIDAgNy44MjkuNzljNC4xNDUgMCA3LjM2OC0uNzI0IDkuNjA1LTIuMTcxYzIuMjM3LTEuNDQ4IDMuNDIxLTMuNTUzIDMuNDIxLTYuMjVjMC0xLjg0Mi0uNTkyLTMuMzU2LTEuNzc2LTQuNjA2cy0zLjQyMS0yLjM2OC02LjY0NS0zLjQyMWwtOS41MzktMi45NmMtNC44MDMtMS41MTMtOC4zNTYtMy43NS0xMC41MjctNi43MWMtMi4xNzEtMi44OTUtMy4yODktNi4xMi0zLjI4OS05LjU0cTAtNC4xNDQgMS43NzYtNy4zMDNjMS4xODQtMi4xMDUgMi43NjMtMy45NDcgNC43MzctNS4zOTRjMS45NzQtMS41MTQgNC4yMTEtMi42MzIgNi44NDItMy40MjJjMi42MzItLjc5IDUuMzk1LTEuMTE4IDguMjktMS4xMThjMS40NDcgMCAyLjk2LjA2NiA0LjQwOC4yNjNjMS41MTMuMTk3IDIuODk0LjQ2IDQuMjc2LjcyNGMxLjMxNi4zMjkgMi41NjYuNjU4IDMuNzUgMS4wNTNxMS43NzYuNTkxIDIuNzYzIDEuMTg0Yy45MjEuNTI2IDEuNTc5IDEuMDUyIDEuOTc0IDEuNjQ0cS41OTIuNzkuNTkyIDIuMTcydjMuMDkyYzAgMS4zODEtLjUyNiAyLjEwNS0xLjUxMyAyLjEwNWMtLjUyNyAwLTEuMzgyLS4yNjMtMi41LS43OXEtNS42MjUtMi41NjUtMTIuNjMyLTIuNTY1Yy0zLjc1IDAtNi43MS41OTItOC43NSAxLjg0MnMtMy4wOTIgMy4xNTgtMy4wOTIgNS44NTVjMCAxLjg0Mi42NTggMy40MjEgMS45NzQgNC42NzFzMy43NSAyLjUgNy4yMzcgMy42MThsOS4zNDIgMi45NmM0LjczNiAxLjUxNCA4LjE1OCAzLjYxOSAxMC4xOTcgNi4zMTdzMy4wMjYgNS43ODkgMy4wMjYgOS4yMWMwIDIuODI5LS41OTIgNS4zOTUtMS43MSA3LjYzMmMtMS4xODQgMi4yMzctMi43NjMgNC4yMS00LjgwMyA1Ljc4OWMtMi4wMzkgMS42NDUtNC40NzQgMi44MjktNy4zMDIgMy42ODVjLTIuOTYxLjkyMS02LjA1MyAxLjM4MS05LjQwOCAxLjM4MSIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIwNy44MzcgMTYyLjgxNmMtMjEuNjQ1IDE1Ljk4Ny01My4wOTIgMjQuNDc0LTgwLjEzMiAyNC40NzRjLTM3Ljg5NCAwLTcyLjA0LTE0LjAxNC05Ny44MjktMzcuMzAzYy0yLjA0LTEuODQyLS4xOTctNC4zNDIgMi4yMzctMi44OTVjMjcuODk1IDE2LjE4NCA2Mi4zMDMgMjUuOTg3IDk3Ljg5NSAyNS45ODdjMjQuMDEzIDAgNTAuMzk1LTUgNzQuNjcxLTE1LjI2M2MzLjYxOC0xLjY0NSA2LjcxIDIuMzY4IDMuMTU4IDUiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIxNi44NSAxNTIuNTUzYy0yLjc2My0zLjU1My0xOC4yODktMS43MTEtMjUuMzI5LS44NTZjLTIuMTA1LjI2NC0yLjQzNC0xLjU3OS0uNTI2LTIuOTZjMTIuMzY4LTguNjg0IDMyLjY5Ny02LjE4NCAzNS4wNjYtMy4yOWMyLjM2OCAyLjk2MS0uNjU4IDIzLjI5LTEyLjIzNyAzMy4wMjdjLTEuNzc3IDEuNTEzLTMuNDg3LjcyMy0yLjY5OC0xLjI1YzIuNjMyLTYuNTEzIDguNDg3LTIxLjE4NSA1LjcyNC0yNC42NzEiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=", - resources: 85, - added: "Jan 10, 2024", - }, - { - id: 5, - account: "aws", - provider_id: 740350143848, - connected: true, - group: "Sales", - scan_status: "active", - scan_status_time: "7 minutes, 45 seconds", - last_scan: "Jul 17, 2024 12:46pm UTC", - next_scan: "Jul 18, 2024 12:46pm UTC", - provider_img: - "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgdmlld0JveD0iMCAwIDI1NiAyNTYiPjxnIGZpbGw9Im5vbmUiPjxyZWN0IHdpZHRoPSIyNTYiIGhlaWdodD0iMjU2IiBmaWxsPSIjZjRmMmVkIiByeD0iNjAiLz48cGF0aCBmaWxsPSIjMjUyZjNlIiBkPSJNODQuNzQ1IDExMS45NjFjMCAyLjQzNC4yNjMgNC40MDcuNzIzIDUuODU1YTM1IDM1IDAgMCAwIDIuMTA2IDQuNzM3Yy4zMjkuNTI2LjQ2IDEuMDUyLjQ2IDEuNTEzYzAgLjY1OC0uMzk1IDEuMzE2LTEuMjUgMS45NzNsLTQuMTQ1IDIuNzY0Yy0uNTkyLjM5NC0xLjE4NC41OTItMS43MS41OTJjLS42NTggMC0xLjMxNi0uMzI5LTEuOTc0LS45MjFhMjAuNCAyMC40IDAgMCAxLTIuMzY4LTMuMDkyYTUxIDUxIDAgMCAxLTIuMDQtMy44ODJxLTcuNjk3IDkuMDgtMTkuMzQyIDkuMDc5Yy01LjUyNiAwLTkuOTM0LTEuNTc5LTEzLjE1OC00LjczN2MtMy4yMjMtMy4xNTgtNC44NjgtNy4zNjgtNC44NjgtMTIuNjMxYzAtNS41OTMgMS45NzQtMTAuMTMyIDUuOTg3LTEzLjU1M3M5LjM0Mi01LjEzMiAxNi4xMTgtNS4xMzJjMi4yMzcgMCA0LjU0LjE5OCA2Ljk3NC41MjdzNC45MzQuODU1IDcuNTY2IDEuNDQ3di00LjgwM2MwLTUtMS4wNTMtOC40ODctMy4wOTItMTAuNTI2Yy0yLjEwNi0yLjA0LTUuNjU4LTMuMDI2LTEwLjcyNC0zLjAyNmMtMi4zMDMgMC00LjY3MS4yNjMtNy4xMDUuODU1cy00LjgwMyAxLjMxNi03LjEwNiAyLjIzN2ExOSAxOSAwIDAgMS0yLjMwMi44NTVjLS40Ni4xMzItLjc5LjE5OC0xLjA1My4xOThjLS45MiAwLTEuMzgyLS42NTgtMS4zODItMi4wNHYtMy4yMjRjMC0xLjA1Mi4xMzItMS44NDIuNDYxLTIuMzAycy45MjEtLjkyMSAxLjg0Mi0xLjM4MnEzLjQ1NC0xLjc3NiA4LjI5LTIuOTZjMy4yMjMtLjg1NiA2LjY0NC0xLjI1IDEwLjI2My0xLjI1YzcuODI5IDAgMTMuNTUyIDEuNzc2IDE3LjIzNyA1LjMyOGMzLjYxOCAzLjU1MyA1LjQ2IDguOTQ4IDUuNDYgMTYuMTg1djIxLjMxNnptLTI2LjcxIDEwYzIuMTcgMCA0LjQwNy0uMzk1IDYuNzc2LTEuMTg1YzIuMzY4LS43ODkgNC40NzMtMi4yMzcgNi4yNS00LjIxYzEuMDUyLTEuMjUgMS44NDItMi42MzIgMi4yMzYtNC4yMTFzLjY1OC0zLjQ4Ny42NTgtNS43MjN2LTIuNzY0YTU1IDU1IDAgMCAwLTYuMDUyLTEuMTE4YTUwIDUwIDAgMCAwLTYuMTg1LS4zOTVjLTQuNDA4IDAtNy42MzEuODU2LTkuODAyIDIuNjMycy0zLjIyNCA0LjI3Ni0zLjIyNCA3LjU2NmMwIDMuMDkyLjc5IDUuMzk0IDIuNDM0IDYuOTczYzEuNTggMS42NDUgMy44ODIgMi40MzUgNi45MDggMi40MzVtNTIuODI4IDcuMTA1Yy0xLjE4NCAwLTEuOTc0LS4xOTgtMi41LS42NThjLS41MjYtLjM5NS0uOTg3LTEuMzE2LTEuMzgxLTIuNTY2bC0xNS40Ni01MC44NTVjLS4zOTYtMS4zMTYtLjU5My0yLjE3MS0uNTkzLTIuNjMyYzAtMS4wNTIuNTI2LTEuNjQ1IDEuNTc5LTEuNjQ1aDYuNDQ3YzEuMjUgMCAyLjEwNi4xOTggMi41NjYuNjU4Yy41MjYuMzk1LjkyMSAxLjMxNiAxLjMxNiAyLjU2NmwxMS4wNTIgNDMuNTUzbDEwLjI2NC00My41NTNjLjMyOS0xLjMxNi43MjMtMi4xNyAxLjI1LTIuNTY2Yy41MjYtLjM5NCAxLjQ0Ny0uNjU3IDIuNjMxLS42NTdoNS4yNjNjMS4yNSAwIDIuMTA2LjE5NyAyLjYzMi42NTdjLjUyNi4zOTUuOTg3IDEuMzE2IDEuMjUgMi41NjZsMTAuMzk1IDQ0LjA3OWwxMS4zODEtNDQuMDc5Yy4zOTUtMS4zMTYuODU2LTIuMTcgMS4zMTYtMi41NjZjLjUyNi0uMzk0IDEuMzgyLS42NTcgMi41NjYtLjY1N2g2LjExOGMxLjA1MyAwIDEuNjQ1LjUyNiAxLjY0NSAxLjY0NGMwIC4zMy0uMDY2LjY1OC0uMTMyIDEuMDUzYy0uMDY1LjM5NS0uMTk3LjkyLS40NiAxLjY0NWwtMTUuODU1IDUwLjg1NXEtLjU5MyAxLjk3NC0xLjM4MiAyLjU2NmMtLjUyNi4zOTQtMS4zODIuNjU4LTIuNS42NThoLTUuNjU4Yy0xLjI1IDAtMi4xMDUtLjE5OC0yLjYzMS0uNjU4Yy0uNTI3LS40NjEtLjk4Ny0xLjMxNi0xLjI1LTIuNjMybC0xMC4xOTgtNDIuNDM0bC0xMC4xMzEgNDIuMzY4Yy0uMzI5IDEuMzE2LS43MjQgMi4xNzEtMS4yNSAyLjYzMmMtLjUyNy40Ni0xLjQ0OC42NTgtMi42MzIuNjU4em04NC41NCAxLjc3NmMtMy40MjEgMC02Ljg0Mi0uMzk1LTEwLjEzMi0xLjE4NGMtMy4yODktLjc5LTUuODU1LTEuNjQ1LTcuNTY2LTIuNjMyYy0xLjA1Mi0uNTkyLTEuNzc2LTEuMjUtMi4wMzktMS44NDJhNC42NSA0LjY1IDAgMCAxLS4zOTUtMS44NDJ2LTMuMzU1YzAtMS4zODIuNTI2LTIuMDQgMS41MTMtMi4wNHEuNTkzIDAgMS4xODQuMTk4Yy4zOTUuMTMxLjk4Ny4zOTQgMS42NDUuNjU4YTM1LjggMzUuOCAwIDAgMCA3LjIzNyAyLjMwMmEzOS41IDM5LjUgMCAwIDAgNy44MjkuNzljNC4xNDUgMCA3LjM2OC0uNzI0IDkuNjA1LTIuMTcxYzIuMjM3LTEuNDQ4IDMuNDIxLTMuNTUzIDMuNDIxLTYuMjVjMC0xLjg0Mi0uNTkyLTMuMzU2LTEuNzc2LTQuNjA2cy0zLjQyMS0yLjM2OC02LjY0NS0zLjQyMWwtOS41MzktMi45NmMtNC44MDMtMS41MTMtOC4zNTYtMy43NS0xMC41MjctNi43MWMtMi4xNzEtMi44OTUtMy4yODktNi4xMi0zLjI4OS05LjU0cTAtNC4xNDQgMS43NzYtNy4zMDNjMS4xODQtMi4xMDUgMi43NjMtMy45NDcgNC43MzctNS4zOTRjMS45NzQtMS41MTQgNC4yMTEtMi42MzIgNi44NDItMy40MjJjMi42MzItLjc5IDUuMzk1LTEuMTE4IDguMjktMS4xMThjMS40NDcgMCAyLjk2LjA2NiA0LjQwOC4yNjNjMS41MTMuMTk3IDIuODk0LjQ2IDQuMjc2LjcyNGMxLjMxNi4zMjkgMi41NjYuNjU4IDMuNzUgMS4wNTNxMS43NzYuNTkxIDIuNzYzIDEuMTg0Yy45MjEuNTI2IDEuNTc5IDEuMDUyIDEuOTc0IDEuNjQ0cS41OTIuNzkuNTkyIDIuMTcydjMuMDkyYzAgMS4zODEtLjUyNiAyLjEwNS0xLjUxMyAyLjEwNWMtLjUyNyAwLTEuMzgyLS4yNjMtMi41LS43OXEtNS42MjUtMi41NjUtMTIuNjMyLTIuNTY1Yy0zLjc1IDAtNi43MS41OTItOC43NSAxLjg0MnMtMy4wOTIgMy4xNTgtMy4wOTIgNS44NTVjMCAxLjg0Mi42NTggMy40MjEgMS45NzQgNC42NzFzMy43NSAyLjUgNy4yMzcgMy42MThsOS4zNDIgMi45NmM0LjczNiAxLjUxNCA4LjE1OCAzLjYxOSAxMC4xOTcgNi4zMTdzMy4wMjYgNS43ODkgMy4wMjYgOS4yMWMwIDIuODI5LS41OTIgNS4zOTUtMS43MSA3LjYzMmMtMS4xODQgMi4yMzctMi43NjMgNC4yMS00LjgwMyA1Ljc4OWMtMi4wMzkgMS42NDUtNC40NzQgMi44MjktNy4zMDIgMy42ODVjLTIuOTYxLjkyMS02LjA1MyAxLjM4MS05LjQwOCAxLjM4MSIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIwNy44MzcgMTYyLjgxNmMtMjEuNjQ1IDE1Ljk4Ny01My4wOTIgMjQuNDc0LTgwLjEzMiAyNC40NzRjLTM3Ljg5NCAwLTcyLjA0LTE0LjAxNC05Ny44MjktMzcuMzAzYy0yLjA0LTEuODQyLS4xOTctNC4zNDIgMi4yMzctMi44OTVjMjcuODk1IDE2LjE4NCA2Mi4zMDMgMjUuOTg3IDk3Ljg5NSAyNS45ODdjMjQuMDEzIDAgNTAuMzk1LTUgNzQuNjcxLTE1LjI2M2MzLjYxOC0xLjY0NSA2LjcxIDIuMzY4IDMuMTU4IDUiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIxNi44NSAxNTIuNTUzYy0yLjc2My0zLjU1My0xOC4yODktMS43MTEtMjUuMzI5LS44NTZjLTIuMTA1LjI2NC0yLjQzNC0xLjU3OS0uNTI2LTIuOTZjMTIuMzY4LTguNjg0IDMyLjY5Ny02LjE4NCAzNS4wNjYtMy4yOWMyLjM2OCAyLjk2MS0uNjU4IDIzLjI5LTEyLjIzNyAzMy4wMjdjLTEuNzc3IDEuNTEzLTMuNDg3LjcyMy0yLjY5OC0xLjI1YzIuNjMyLTYuNTEzIDguNDg3LTIxLjE4NSA1LjcyNC0yNC42NzEiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=", - resources: 156, - added: "May 5, 2024", - }, - { - id: 6, - account: "aws", - provider_id: 740350143849, - connected: true, - group: "Management", - scan_status: "active", - scan_status_time: "3 minutes, 10 seconds", - last_scan: "Jul 17, 2024 11:46am UTC", - next_scan: "Jul 18, 2024 11:46am UTC", - provider_img: - "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgdmlld0JveD0iMCAwIDI1NiAyNTYiPjxnIGZpbGw9Im5vbmUiPjxyZWN0IHdpZHRoPSIyNTYiIGhlaWdodD0iMjU2IiBmaWxsPSIjZjRmMmVkIiByeD0iNjAiLz48cGF0aCBmaWxsPSIjMjUyZjNlIiBkPSJNODQuNzQ1IDExMS45NjFjMCAyLjQzNC4yNjMgNC40MDcuNzIzIDUuODU1YTM1IDM1IDAgMCAwIDIuMTA2IDQuNzM3Yy4zMjkuNTI2LjQ2IDEuMDUyLjQ2IDEuNTEzYzAgLjY1OC0uMzk1IDEuMzE2LTEuMjUgMS45NzNsLTQuMTQ1IDIuNzY0Yy0uNTkyLjM5NC0xLjE4NC41OTItMS43MS41OTJjLS42NTggMC0xLjMxNi0uMzI5LTEuOTc0LS45MjFhMjAuNCAyMC40IDAgMCAxLTIuMzY4LTMuMDkyYTUxIDUxIDAgMCAxLTIuMDQtMy44ODJxLTcuNjk3IDkuMDgtMTkuMzQyIDkuMDc5Yy01LjUyNiAwLTkuOTM0LTEuNTc5LTEzLjE1OC00LjczN2MtMy4yMjMtMy4xNTgtNC44NjgtNy4zNjgtNC44NjgtMTIuNjMxYzAtNS41OTMgMS45NzQtMTAuMTMyIDUuOTg3LTEzLjU1M3M5LjM0Mi01LjEzMiAxNi4xMTgtNS4xMzJjMi4yMzcgMCA0LjU0LjE5OCA2Ljk3NC41MjdzNC45MzQuODU1IDcuNTY2IDEuNDQ3di00LjgwM2MwLTUtMS4wNTMtOC40ODctMy4wOTItMTAuNTI2Yy0yLjEwNi0yLjA0LTUuNjU4LTMuMDI2LTEwLjcyNC0zLjAyNmMtMi4zMDMgMC00LjY3MS4yNjMtNy4xMDUuODU1cy00LjgwMyAxLjMxNi03LjEwNiAyLjIzN2ExOSAxOSAwIDAgMS0yLjMwMi44NTVjLS40Ni4xMzItLjc5LjE5OC0xLjA1My4xOThjLS45MiAwLTEuMzgyLS42NTgtMS4zODItMi4wNHYtMy4yMjRjMC0xLjA1Mi4xMzItMS44NDIuNDYxLTIuMzAycy45MjEtLjkyMSAxLjg0Mi0xLjM4MnEzLjQ1NC0xLjc3NiA4LjI5LTIuOTZjMy4yMjMtLjg1NiA2LjY0NC0xLjI1IDEwLjI2My0xLjI1YzcuODI5IDAgMTMuNTUyIDEuNzc2IDE3LjIzNyA1LjMyOGMzLjYxOCAzLjU1MyA1LjQ2IDguOTQ4IDUuNDYgMTYuMTg1djIxLjMxNnptLTI2LjcxIDEwYzIuMTcgMCA0LjQwNy0uMzk1IDYuNzc2LTEuMTg1YzIuMzY4LS43ODkgNC40NzMtMi4yMzcgNi4yNS00LjIxYzEuMDUyLTEuMjUgMS44NDItMi42MzIgMi4yMzYtNC4yMTFzLjY1OC0zLjQ4Ny42NTgtNS43MjN2LTIuNzY0YTU1IDU1IDAgMCAwLTYuMDUyLTEuMTE4YTUwIDUwIDAgMCAwLTYuMTg1LS4zOTVjLTQuNDA4IDAtNy42MzEuODU2LTkuODAyIDIuNjMycy0zLjIyNCA0LjI3Ni0zLjIyNCA3LjU2NmMwIDMuMDkyLjc5IDUuMzk0IDIuNDM0IDYuOTczYzEuNTggMS42NDUgMy44ODIgMi40MzUgNi45MDggMi40MzVtNTIuODI4IDcuMTA1Yy0xLjE4NCAwLTEuOTc0LS4xOTgtMi41LS42NThjLS41MjYtLjM5NS0uOTg3LTEuMzE2LTEuMzgxLTIuNTY2bC0xNS40Ni01MC44NTVjLS4zOTYtMS4zMTYtLjU5My0yLjE3MS0uNTkzLTIuNjMyYzAtMS4wNTIuNTI2LTEuNjQ1IDEuNTc5LTEuNjQ1aDYuNDQ3YzEuMjUgMCAyLjEwNi4xOTggMi41NjYuNjU4Yy41MjYuMzk1LjkyMSAxLjMxNiAxLjMxNiAyLjU2NmwxMS4wNTIgNDMuNTUzbDEwLjI2NC00My41NTNjLjMyOS0xLjMxNi43MjMtMi4xNyAxLjI1LTIuNTY2Yy41MjYtLjM5NCAxLjQ0Ny0uNjU3IDIuNjMxLS42NTdoNS4yNjNjMS4yNSAwIDIuMTA2LjE5NyAyLjYzMi42NTdjLjUyNi4zOTUuOTg3IDEuMzE2IDEuMjUgMi41NjZsMTAuMzk1IDQ0LjA3OWwxMS4zODEtNDQuMDc5Yy4zOTUtMS4zMTYuODU2LTIuMTcgMS4zMTYtMi41NjZjLjUyNi0uMzk0IDEuMzgyLS42NTcgMi41NjYtLjY1N2g2LjExOGMxLjA1MyAwIDEuNjQ1LjUyNiAxLjY0NSAxLjY0NGMwIC4zMy0uMDY2LjY1OC0uMTMyIDEuMDUzYy0uMDY1LjM5NS0uMTk3LjkyLS40NiAxLjY0NWwtMTUuODU1IDUwLjg1NXEtLjU5MyAxLjk3NC0xLjM4MiAyLjU2NmMtLjUyNi4zOTQtMS4zODIuNjU4LTIuNS42NThoLTUuNjU4Yy0xLjI1IDAtMi4xMDUtLjE5OC0yLjYzMS0uNjU4Yy0uNTI3LS40NjEtLjk4Ny0xLjMxNi0xLjI1LTIuNjMybC0xMC4xOTgtNDIuNDM0bC0xMC4xMzEgNDIuMzY4Yy0uMzI5IDEuMzE2LS43MjQgMi4xNzEtMS4yNSAyLjYzMmMtLjUyNy40Ni0xLjQ0OC42NTgtMi42MzIuNjU4em04NC41NCAxLjc3NmMtMy40MjEgMC02Ljg0Mi0uMzk1LTEwLjEzMi0xLjE4NGMtMy4yODktLjc5LTUuODU1LTEuNjQ1LTcuNTY2LTIuNjMyYy0xLjA1Mi0uNTkyLTEuNzc2LTEuMjUtMi4wMzktMS44NDJhNC42NSA0LjY1IDAgMCAxLS4zOTUtMS44NDJ2LTMuMzU1YzAtMS4zODIuNTI2LTIuMDQgMS41MTMtMi4wNHEuNTkzIDAgMS4xODQuMTk4Yy4zOTUuMTMxLjk4Ny4zOTQgMS42NDUuNjU4YTM1LjggMzUuOCAwIDAgMCA3LjIzNyAyLjMwMmEzOS41IDM5LjUgMCAwIDAgNy44MjkuNzljNC4xNDUgMCA3LjM2OC0uNzI0IDkuNjA1LTIuMTcxYzIuMjM3LTEuNDQ4IDMuNDIxLTMuNTUzIDMuNDIxLTYuMjVjMC0xLjg0Mi0uNTkyLTMuMzU2LTEuNzc2LTQuNjA2cy0zLjQyMS0yLjM2OC02LjY0NS0zLjQyMWwtOS41MzktMi45NmMtNC44MDMtMS41MTMtOC4zNTYtMy43NS0xMC41MjctNi43MWMtMi4xNzEtMi44OTUtMy4yODktNi4xMi0zLjI4OS05LjU0cTAtNC4xNDQgMS43NzYtNy4zMDNjMS4xODQtMi4xMDUgMi43NjMtMy45NDcgNC43MzctNS4zOTRjMS45NzQtMS41MTQgNC4yMTEtMi42MzIgNi44NDItMy40MjJjMi42MzItLjc5IDUuMzk1LTEuMTE4IDguMjktMS4xMThjMS40NDcgMCAyLjk2LjA2NiA0LjQwOC4yNjNjMS41MTMuMTk3IDIuODk0LjQ2IDQuMjc2LjcyNGMxLjMxNi4zMjkgMi41NjYuNjU4IDMuNzUgMS4wNTNxMS43NzYuNTkxIDIuNzYzIDEuMTg0Yy45MjEuNTI2IDEuNTc5IDEuMDUyIDEuOTc0IDEuNjQ0cS41OTIuNzkuNTkyIDIuMTcydjMuMDkyYzAgMS4zODEtLjUyNiAyLjEwNS0xLjUxMyAyLjEwNWMtLjUyNyAwLTEuMzgyLS4yNjMtMi41LS43OXEtNS42MjUtMi41NjUtMTIuNjMyLTIuNTY1Yy0zLjc1IDAtNi43MS41OTItOC43NSAxLjg0MnMtMy4wOTIgMy4xNTgtMy4wOTIgNS44NTVjMCAxLjg0Mi42NTggMy40MjEgMS45NzQgNC42NzFzMy43NSAyLjUgNy4yMzcgMy42MThsOS4zNDIgMi45NmM0LjczNiAxLjUxNCA4LjE1OCAzLjYxOSAxMC4xOTcgNi4zMTdzMy4wMjYgNS43ODkgMy4wMjYgOS4yMWMwIDIuODI5LS41OTIgNS4zOTUtMS43MSA3LjYzMmMtMS4xODQgMi4yMzctMi43NjMgNC4yMS00LjgwMyA1Ljc4OWMtMi4wMzkgMS42NDUtNC40NzQgMi44MjktNy4zMDIgMy42ODVjLTIuOTYxLjkyMS02LjA1MyAxLjM4MS05LjQwOCAxLjM4MSIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIwNy44MzcgMTYyLjgxNmMtMjEuNjQ1IDE1Ljk4Ny01My4wOTIgMjQuNDc0LTgwLjEzMiAyNC40NzRjLTM3Ljg5NCAwLTcyLjA0LTE0LjAxNC05Ny44MjktMzcuMzAzYy0yLjA0LTEuODQyLS4xOTctNC4zNDIgMi4yMzctMi44OTVjMjcuODk1IDE2LjE4NCA2Mi4zMDMgMjUuOTg3IDk3Ljg5NSAyNS45ODdjMjQuMDEzIDAgNTAuMzk1LTUgNzQuNjcxLTE1LjI2M2MzLjYxOC0xLjY0NSA2LjcxIDIuMzY4IDMuMTU4IDUiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIxNi44NSAxNTIuNTUzYy0yLjc2My0zLjU1My0xOC4yODktMS43MTEtMjUuMzI5LS44NTZjLTIuMTA1LjI2NC0yLjQzNC0xLjU3OS0uNTI2LTIuOTZjMTIuMzY4LTguNjg0IDMyLjY5Ny02LjE4NCAzNS4wNjYtMy4yOWMyLjM2OCAyLjk2MS0uNjU4IDIzLjI5LTEyLjIzNyAzMy4wMjdjLTEuNzc3IDEuNTEzLTMuNDg3LjcyMy0yLjY5OC0xLjI1YzIuNjMyLTYuNTEzIDguNDg3LTIxLjE4NSA1LjcyNC0yNC42NzEiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=", - resources: 98, - added: "Mar 22, 2024", - }, - { - id: 7, - account: "aws", - provider_id: 740350143850, - connected: false, - group: "Design", - scan_status: "paused", - scan_status_time: "12 minutes, 5 seconds", - last_scan: "Jul 17, 2024 10:46am UTC", - next_scan: "Jul 18, 2024 10:46am UTC", - provider_img: - "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgdmlld0JveD0iMCAwIDI1NiAyNTYiPjxnIGZpbGw9Im5vbmUiPjxyZWN0IHdpZHRoPSIyNTYiIGhlaWdodD0iMjU2IiBmaWxsPSIjZjRmMmVkIiByeD0iNjAiLz48cGF0aCBmaWxsPSIjMjUyZjNlIiBkPSJNODQuNzQ1IDExMS45NjFjMCAyLjQzNC4yNjMgNC40MDcuNzIzIDUuODU1YTM1IDM1IDAgMCAwIDIuMTA2IDQuNzM3Yy4zMjkuNTI2LjQ2IDEuMDUyLjQ2IDEuNTEzYzAgLjY1OC0uMzk1IDEuMzE2LTEuMjUgMS45NzNsLTQuMTQ1IDIuNzY0Yy0uNTkyLjM5NC0xLjE4NC41OTItMS43MS41OTJjLS42NTggMC0xLjMxNi0uMzI5LTEuOTc0LS45MjFhMjAuNCAyMC40IDAgMCAxLTIuMzY4LTMuMDkyYTUxIDUxIDAgMCAxLTIuMDQtMy44ODJxLTcuNjk3IDkuMDgtMTkuMzQyIDkuMDc5Yy01LjUyNiAwLTkuOTM0LTEuNTc5LTEzLjE1OC00LjczN2MtMy4yMjMtMy4xNTgtNC44NjgtNy4zNjgtNC44NjgtMTIuNjMxYzAtNS41OTMgMS45NzQtMTAuMTMyIDUuOTg3LTEzLjU1M3M5LjM0Mi01LjEzMiAxNi4xMTgtNS4xMzJjMi4yMzcgMCA0LjU0LjE5OCA2Ljk3NC41MjdzNC45MzQuODU1IDcuNTY2IDEuNDQ3di00LjgwM2MwLTUtMS4wNTMtOC40ODctMy4wOTItMTAuNTI2Yy0yLjEwNi0yLjA0LTUuNjU4LTMuMDI2LTEwLjcyNC0zLjAyNmMtMi4zMDMgMC00LjY3MS4yNjMtNy4xMDUuODU1cy00LjgwMyAxLjMxNi03LjEwNiAyLjIzN2ExOSAxOSAwIDAgMS0yLjMwMi44NTVjLS40Ni4xMzItLjc5LjE5OC0xLjA1My4xOThjLS45MiAwLTEuMzgyLS42NTgtMS4zODItMi4wNHYtMy4yMjRjMC0xLjA1Mi4xMzItMS44NDIuNDYxLTIuMzAycy45MjEtLjkyMSAxLjg0Mi0xLjM4MnEzLjQ1NC0xLjc3NiA4LjI5LTIuOTZjMy4yMjMtLjg1NiA2LjY0NC0xLjI1IDEwLjI2My0xLjI1YzcuODI5IDAgMTMuNTUyIDEuNzc2IDE3LjIzNyA1LjMyOGMzLjYxOCAzLjU1MyA1LjQ2IDguOTQ4IDUuNDYgMTYuMTg1djIxLjMxNnptLTI2LjcxIDEwYzIuMTcgMCA0LjQwNy0uMzk1IDYuNzc2LTEuMTg1YzIuMzY4LS43ODkgNC40NzMtMi4yMzcgNi4yNS00LjIxYzEuMDUyLTEuMjUgMS44NDItMi42MzIgMi4yMzYtNC4yMTFzLjY1OC0zLjQ4Ny42NTgtNS43MjN2LTIuNzY0YTU1IDU1IDAgMCAwLTYuMDUyLTEuMTE4YTUwIDUwIDAgMCAwLTYuMTg1LS4zOTVjLTQuNDA4IDAtNy42MzEuODU2LTkuODAyIDIuNjMycy0zLjIyNCA0LjI3Ni0zLjIyNCA3LjU2NmMwIDMuMDkyLjc5IDUuMzk0IDIuNDM0IDYuOTczYzEuNTggMS42NDUgMy44ODIgMi40MzUgNi45MDggMi40MzVtNTIuODI4IDcuMTA1Yy0xLjE4NCAwLTEuOTc0LS4xOTgtMi41LS42NThjLS41MjYtLjM5NS0uOTg3LTEuMzE2LTEuMzgxLTIuNTY2bC0xNS40Ni01MC44NTVjLS4zOTYtMS4zMTYtLjU5My0yLjE3MS0uNTkzLTIuNjMyYzAtMS4wNTIuNTI2LTEuNjQ1IDEuNTc5LTEuNjQ1aDYuNDQ3YzEuMjUgMCAyLjEwNi4xOTggMi41NjYuNjU4Yy41MjYuMzk1LjkyMSAxLjMxNiAxLjMxNiAyLjU2NmwxMS4wNTIgNDMuNTUzbDEwLjI2NC00My41NTNjLjMyOS0xLjMxNi43MjMtMi4xNyAxLjI1LTIuNTY2Yy41MjYtLjM5NCAxLjQ0Ny0uNjU3IDIuNjMxLS42NTdoNS4yNjNjMS4yNSAwIDIuMTA2LjE5NyAyLjYzMi42NTdjLjUyNi4zOTUuOTg3IDEuMzE2IDEuMjUgMi41NjZsMTAuMzk1IDQ0LjA3OWwxMS4zODEtNDQuMDc5Yy4zOTUtMS4zMTYuODU2LTIuMTcgMS4zMTYtMi41NjZjLjUyNi0uMzk0IDEuMzgyLS42NTcgMi41NjYtLjY1N2g2LjExOGMxLjA1MyAwIDEuNjQ1LjUyNiAxLjY0NSAxLjY0NGMwIC4zMy0uMDY2LjY1OC0uMTMyIDEuMDUzYy0uMDY1LjM5NS0uMTk3LjkyLS40NiAxLjY0NWwtMTUuODU1IDUwLjg1NXEtLjU5MyAxLjk3NC0xLjM4MiAyLjU2NmMtLjUyNi4zOTQtMS4zODIuNjU4LTIuNS42NThoLTUuNjU4Yy0xLjI1IDAtMi4xMDUtLjE5OC0yLjYzMS0uNjU4Yy0uNTI3LS40NjEtLjk4Ny0xLjMxNi0xLjI1LTIuNjMybC0xMC4xOTgtNDIuNDM0bC0xMC4xMzEgNDIuMzY4Yy0uMzI5IDEuMzE2LS43MjQgMi4xNzEtMS4yNSAyLjYzMmMtLjUyNy40Ni0xLjQ0OC42NTgtMi42MzIuNjU4em04NC41NCAxLjc3NmMtMy40MjEgMC02Ljg0Mi0uMzk1LTEwLjEzMi0xLjE4NGMtMy4yODktLjc5LTUuODU1LTEuNjQ1LTcuNTY2LTIuNjMyYy0xLjA1Mi0uNTkyLTEuNzc2LTEuMjUtMi4wMzktMS44NDJhNC42NSA0LjY1IDAgMCAxLS4zOTUtMS44NDJ2LTMuMzU1YzAtMS4zODIuNTI2LTIuMDQgMS41MTMtMi4wNHEuNTkzIDAgMS4xODQuMTk4Yy4zOTUuMTMxLjk4Ny4zOTQgMS42NDUuNjU4YTM1LjggMzUuOCAwIDAgMCA3LjIzNyAyLjMwMmEzOS41IDM5LjUgMCAwIDAgNy44MjkuNzljNC4xNDUgMCA3LjM2OC0uNzI0IDkuNjA1LTIuMTcxYzIuMjM3LTEuNDQ4IDMuNDIxLTMuNTUzIDMuNDIxLTYuMjVjMC0xLjg0Mi0uNTkyLTMuMzU2LTEuNzc2LTQuNjA2cy0zLjQyMS0yLjM2OC02LjY0NS0zLjQyMWwtOS41MzktMi45NmMtNC44MDMtMS41MTMtOC4zNTYtMy43NS0xMC41MjctNi43MWMtMi4xNzEtMi44OTUtMy4yODktNi4xMi0zLjI4OS05LjU0cTAtNC4xNDQgMS43NzYtNy4zMDNjMS4xODQtMi4xMDUgMi43NjMtMy45NDcgNC43MzctNS4zOTRjMS45NzQtMS41MTQgNC4yMTEtMi42MzIgNi44NDItMy40MjJjMi42MzItLjc5IDUuMzk1LTEuMTE4IDguMjktMS4xMThjMS40NDcgMCAyLjk2LjA2NiA0LjQwOC4yNjNjMS41MTMuMTk3IDIuODk0LjQ2IDQuMjc2LjcyNGMxLjMxNi4zMjkgMi41NjYuNjU4IDMuNzUgMS4wNTNxMS43NzYuNTkxIDIuNzYzIDEuMTg0Yy45MjEuNTI2IDEuNTc5IDEuMDUyIDEuOTc0IDEuNjQ0cS41OTIuNzkuNTkyIDIuMTcydjMuMDkyYzAgMS4zODEtLjUyNiAyLjEwNS0xLjUxMyAyLjEwNWMtLjUyNyAwLTEuMzgyLS4yNjMtMi41LS43OXEtNS42MjUtMi41NjUtMTIuNjMyLTIuNTY1Yy0zLjc1IDAtNi43MS41OTItOC43NSAxLjg0MnMtMy4wOTIgMy4xNTgtMy4wOTIgNS44NTVjMCAxLjg0Mi42NTggMy40MjEgMS45NzQgNC42NzFzMy43NSAyLjUgNy4yMzcgMy42MThsOS4zNDIgMi45NmM0LjczNiAxLjUxNCA4LjE1OCAzLjYxOSAxMC4xOTcgNi4zMTdzMy4wMjYgNS43ODkgMy4wMjYgOS4yMWMwIDIuODI5LS41OTIgNS4zOTUtMS43MSA3LjYzMmMtMS4xODQgMi4yMzctMi43NjMgNC4yMS00LjgwMyA1Ljc4OWMtMi4wMzkgMS42NDUtNC40NzQgMi44MjktNy4zMDIgMy42ODVjLTIuOTYxLjkyMS02LjA1MyAxLjM4MS05LjQwOCAxLjM4MSIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIwNy44MzcgMTYyLjgxNmMtMjEuNjQ1IDE1Ljk4Ny01My4wOTIgMjQuNDc0LTgwLjEzMiAyNC40NzRjLTM3Ljg5NCAwLTcyLjA0LTE0LjAxNC05Ny44MjktMzcuMzAzYy0yLjA0LTEuODQyLS4xOTctNC4zNDIgMi4yMzctMi44OTVjMjcuODk1IDE2LjE4NCA2Mi4zMDMgMjUuOTg3IDk3Ljg5NSAyNS45ODdjMjQuMDEzIDAgNTAuMzk1LTUgNzQuNjcxLTE1LjI2M2MzLjYxOC0xLjY0NSA2LjcxIDIuMzY4IDMuMTU4IDUiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIxNi44NSAxNTIuNTUzYy0yLjc2My0zLjU1My0xOC4yODktMS43MTEtMjUuMzI5LS44NTZjLTIuMTA1LjI2NC0yLjQzNC0xLjU3OS0uNTI2LTIuOTZjMTIuMzY4LTguNjg0IDMyLjY5Ny02LjE4NCAzNS4wNjYtMy4yOWMyLjM2OCAyLjk2MS0uNjU4IDIzLjI5LTEyLjIzNyAzMy4wMjdjLTEuNzc3IDEuNTEzLTMuNDg3LjcyMy0yLjY5OC0xLjI1YzIuNjMyLTYuNTEzIDguNDg3LTIxLjE4NSA1LjcyNC0yNC42NzEiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=", - resources: 76, - added: "Feb 28, 2024", - }, - { - id: 8, - account: "aws", - provider_id: 740350143851, - connected: true, - group: "HR", - scan_status: "active", - scan_status_time: "8 minutes, 20 seconds", - last_scan: "Jul 17, 2024 9:46am UTC", - next_scan: "Jul 18, 2024 9:46am UTC", - provider_img: - "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgdmlld0JveD0iMCAwIDI1NiAyNTYiPjxnIGZpbGw9Im5vbmUiPjxyZWN0IHdpZHRoPSIyNTYiIGhlaWdodD0iMjU2IiBmaWxsPSIjZjRmMmVkIiByeD0iNjAiLz48cGF0aCBmaWxsPSIjMjUyZjNlIiBkPSJNODQuNzQ1IDExMS45NjFjMCAyLjQzNC4yNjMgNC40MDcuNzIzIDUuODU1YTM1IDM1IDAgMCAwIDIuMTA2IDQuNzM3Yy4zMjkuNTI2LjQ2IDEuMDUyLjQ2IDEuNTEzYzAgLjY1OC0uMzk1IDEuMzE2LTEuMjUgMS45NzNsLTQuMTQ1IDIuNzY0Yy0uNTkyLjM5NC0xLjE4NC41OTItMS43MS41OTJjLS42NTggMC0xLjMxNi0uMzI5LTEuOTc0LS45MjFhMjAuNCAyMC40IDAgMCAxLTIuMzY4LTMuMDkyYTUxIDUxIDAgMCAxLTIuMDQtMy44ODJxLTcuNjk3IDkuMDgtMTkuMzQyIDkuMDc5Yy01LjUyNiAwLTkuOTM0LTEuNTc5LTEzLjE1OC00LjczN2MtMy4yMjMtMy4xNTgtNC44NjgtNy4zNjgtNC44NjgtMTIuNjMxYzAtNS41OTMgMS45NzQtMTAuMTMyIDUuOTg3LTEzLjU1M3M5LjM0Mi01LjEzMiAxNi4xMTgtNS4xMzJjMi4yMzcgMCA0LjU0LjE5OCA2Ljk3NC41MjdzNC45MzQuODU1IDcuNTY2IDEuNDQ3di00LjgwM2MwLTUtMS4wNTMtOC40ODctMy4wOTItMTAuNTI2Yy0yLjEwNi0yLjA0LTUuNjU4LTMuMDI2LTEwLjcyNC0zLjAyNmMtMi4zMDMgMC00LjY3MS4yNjMtNy4xMDUuODU1cy00LjgwMyAxLjMxNi03LjEwNiAyLjIzN2ExOSAxOSAwIDAgMS0yLjMwMi44NTVjLS40Ni4xMzItLjc5LjE5OC0xLjA1My4xOThjLS45MiAwLTEuMzgyLS42NTgtMS4zODItMi4wNHYtMy4yMjRjMC0xLjA1Mi4xMzItMS44NDIuNDYxLTIuMzAycy45MjEtLjkyMSAxLjg0Mi0xLjM4MnEzLjQ1NC0xLjc3NiA4LjI5LTIuOTZjMy4yMjMtLjg1NiA2LjY0NC0xLjI1IDEwLjI2My0xLjI1YzcuODI5IDAgMTMuNTUyIDEuNzc2IDE3LjIzNyA1LjMyOGMzLjYxOCAzLjU1MyA1LjQ2IDguOTQ4IDUuNDYgMTYuMTg1djIxLjMxNnptLTI2LjcxIDEwYzIuMTcgMCA0LjQwNy0uMzk1IDYuNzc2LTEuMTg1YzIuMzY4LS43ODkgNC40NzMtMi4yMzcgNi4yNS00LjIxYzEuMDUyLTEuMjUgMS44NDItMi42MzIgMi4yMzYtNC4yMTFzLjY1OC0zLjQ4Ny42NTgtNS43MjN2LTIuNzY0YTU1IDU1IDAgMCAwLTYuMDUyLTEuMTE4YTUwIDUwIDAgMCAwLTYuMTg1LS4zOTVjLTQuNDA4IDAtNy42MzEuODU2LTkuODAyIDIuNjMycy0zLjIyNCA0LjI3Ni0zLjIyNCA3LjU2NmMwIDMuMDkyLjc5IDUuMzk0IDIuNDM0IDYuOTczYzEuNTggMS42NDUgMy44ODIgMi40MzUgNi45MDggMi40MzVtNTIuODI4IDcuMTA1Yy0xLjE4NCAwLTEuOTc0LS4xOTgtMi41LS42NThjLS41MjYtLjM5NS0uOTg3LTEuMzE2LTEuMzgxLTIuNTY2bC0xNS40Ni01MC44NTVjLS4zOTYtMS4zMTYtLjU5My0yLjE3MS0uNTkzLTIuNjMyYzAtMS4wNTIuNTI2LTEuNjQ1IDEuNTc5LTEuNjQ1aDYuNDQ3YzEuMjUgMCAyLjEwNi4xOTggMi41NjYuNjU4Yy41MjYuMzk1LjkyMSAxLjMxNiAxLjMxNiAyLjU2NmwxMS4wNTIgNDMuNTUzbDEwLjI2NC00My41NTNjLjMyOS0xLjMxNi43MjMtMi4xNyAxLjI1LTIuNTY2Yy41MjYtLjM5NCAxLjQ0Ny0uNjU3IDIuNjMxLS42NTdoNS4yNjNjMS4yNSAwIDIuMTA2LjE5NyAyLjYzMi42NTdjLjUyNi4zOTUuOTg3IDEuMzE2IDEuMjUgMi41NjZsMTAuMzk1IDQ0LjA3OWwxMS4zODEtNDQuMDc5Yy4zOTUtMS4zMTYuODU2LTIuMTcgMS4zMTYtMi41NjZjLjUyNi0uMzk0IDEuMzgyLS42NTcgMi41NjYtLjY1N2g2LjExOGMxLjA1MyAwIDEuNjQ1LjUyNiAxLjY0NSAxLjY0NGMwIC4zMy0uMDY2LjY1OC0uMTMyIDEuMDUzYy0uMDY1LjM5NS0uMTk3LjkyLS40NiAxLjY0NWwtMTUuODU1IDUwLjg1NXEtLjU5MyAxLjk3NC0xLjM4MiAyLjU2NmMtLjUyNi4zOTQtMS4zODIuNjU4LTIuNS42NThoLTUuNjU4Yy0xLjI1IDAtMi4xMDUtLjE5OC0yLjYzMS0uNjU4Yy0uNTI3LS40NjEtLjk4Ny0xLjMxNi0xLjI1LTIuNjMybC0xMC4xOTgtNDIuNDM0bC0xMC4xMzEgNDIuMzY4Yy0uMzI5IDEuMzE2LS43MjQgMi4xNzEtMS4yNSAyLjYzMmMtLjUyNy40Ni0xLjQ0OC42NTgtMi42MzIuNjU4em04NC41NCAxLjc3NmMtMy40MjEgMC02Ljg0Mi0uMzk1LTEwLjEzMi0xLjE4NGMtMy4yODktLjc5LTUuODU1LTEuNjQ1LTcuNTY2LTIuNjMyYy0xLjA1Mi0uNTkyLTEuNzc2LTEuMjUtMi4wMzktMS44NDJhNC42NSA0LjY1IDAgMCAxLS4zOTUtMS44NDJ2LTMuMzU1YzAtMS4zODIuNTI2LTIuMDQgMS41MTMtMi4wNHEuNTkzIDAgMS4xODQuMTk4Yy4zOTUuMTMxLjk4Ny4zOTQgMS42NDUuNjU4YTM1LjggMzUuOCAwIDAgMCA3LjIzNyAyLjMwMmEzOS41IDM5LjUgMCAwIDAgNy44MjkuNzljNC4xNDUgMCA3LjM2OC0uNzI0IDkuNjA1LTIuMTcxYzIuMjM3LTEuNDQ4IDMuNDIxLTMuNTUzIDMuNDIxLTYuMjVjMC0xLjg0Mi0uNTkyLTMuMzU2LTEuNzc2LTQuNjA2cy0zLjQyMS0yLjM2OC02LjY0NS0zLjQyMWwtOS41MzktMi45NmMtNC44MDMtMS41MTMtOC4zNTYtMy43NS0xMC41MjctNi43MWMtMi4xNzEtMi44OTUtMy4yODktNi4xMi0zLjI4OS05LjU0cTAtNC4xNDQgMS43NzYtNy4zMDNjMS4xODQtMi4xMDUgMi43NjMtMy45NDcgNC43MzctNS4zOTRjMS45NzQtMS41MTQgNC4yMTEtMi42MzIgNi44NDItMy40MjJjMi42MzItLjc5IDUuMzk1LTEuMTE4IDguMjktMS4xMThjMS40NDcgMCAyLjk2LjA2NiA0LjQwOC4yNjNjMS41MTMuMTk3IDIuODk0LjQ2IDQuMjc2LjcyNGMxLjMxNi4zMjkgMi41NjYuNjU4IDMuNzUgMS4wNTNxMS43NzYuNTkxIDIuNzYzIDEuMTg0Yy45MjEuNTI2IDEuNTc5IDEuMDUyIDEuOTc0IDEuNjQ0cS41OTIuNzkuNTkyIDIuMTcydjMuMDkyYzAgMS4zODEtLjUyNiAyLjEwNS0xLjUxMyAyLjEwNWMtLjUyNyAwLTEuMzgyLS4yNjMtMi41LS43OXEtNS42MjUtMi41NjUtMTIuNjMyLTIuNTY1Yy0zLjc1IDAtNi43MS41OTItOC43NSAxLjg0MnMtMy4wOTIgMy4xNTgtMy4wOTIgNS44NTVjMCAxLjg0Mi42NTggMy40MjEgMS45NzQgNC42NzFzMy43NSAyLjUgNy4yMzcgMy42MThsOS4zNDIgMi45NmM0LjczNiAxLjUxNCA4LjE1OCAzLjYxOSAxMC4xOTcgNi4zMTdzMy4wMjYgNS43ODkgMy4wMjYgOS4yMWMwIDIuODI5LS41OTIgNS4zOTUtMS43MSA3LjYzMmMtMS4xODQgMi4yMzctMi43NjMgNC4yMS00LjgwMyA1Ljc4OWMtMi4wMzkgMS42NDUtNC40NzQgMi44MjktNy4zMDIgMy42ODVjLTIuOTYxLjkyMS02LjA1MyAxLjM4MS05LjQwOCAxLjM4MSIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIwNy44MzcgMTYyLjgxNmMtMjEuNjQ1IDE1Ljk4Ny01My4wOTIgMjQuNDc0LTgwLjEzMiAyNC40NzRjLTM3Ljg5NCAwLTcyLjA0LTE0LjAxNC05Ny44MjktMzcuMzAzYy0yLjA0LTEuODQyLS4xOTctNC4zNDIgMi4yMzctMi44OTVjMjcuODk1IDE2LjE4NCA2Mi4zMDMgMjUuOTg3IDk3Ljg5NSAyNS45ODdjMjQuMDEzIDAgNTAuMzk1LTUgNzQuNjcxLTE1LjI2M2MzLjYxOC0xLjY0NSA2LjcxIDIuMzY4IDMuMTU4IDUiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIxNi44NSAxNTIuNTUzYy0yLjc2My0zLjU1My0xOC4yODktMS43MTEtMjUuMzI5LS44NTZjLTIuMTA1LjI2NC0yLjQzNC0xLjU3OS0uNTI2LTIuOTZjMTIuMzY4LTguNjg0IDMyLjY5Ny02LjE4NCAzNS4wNjYtMy4yOWMyLjM2OCAyLjk2MS0uNjU4IDIzLjI5LTEyLjIzNyAzMy4wMjdjLTEuNzc3IDEuNTEzLTMuNDg3LjcyMy0yLjY5OC0xLjI1YzIuNjMyLTYuNTEzIDguNDg3LTIxLjE4NSA1LjcyNC0yNC42NzEiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=", - resources: 110, - added: "Apr 14, 2024", - }, - { - id: 9, - account: "aws", - provider_id: 740350143852, - connected: true, - group: "Finance", - scan_status: "completed", - scan_status_time: "20 minutes, 30 seconds", - last_scan: "Jul 17, 2024 8:46am UTC", - next_scan: "Jul 18, 2024 8:46am UTC", - provider_img: - "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgdmlld0JveD0iMCAwIDI1NiAyNTYiPjxnIGZpbGw9Im5vbmUiPjxyZWN0IHdpZHRoPSIyNTYiIGhlaWdodD0iMjU2IiBmaWxsPSIjZjRmMmVkIiByeD0iNjAiLz48cGF0aCBmaWxsPSIjMjUyZjNlIiBkPSJNODQuNzQ1IDExMS45NjFjMCAyLjQzNC4yNjMgNC40MDcuNzIzIDUuODU1YTM1IDM1IDAgMCAwIDIuMTA2IDQuNzM3Yy4zMjkuNTI2LjQ2IDEuMDUyLjQ2IDEuNTEzYzAgLjY1OC0uMzk1IDEuMzE2LTEuMjUgMS45NzNsLTQuMTQ1IDIuNzY0Yy0uNTkyLjM5NC0xLjE4NC41OTItMS43MS41OTJjLS42NTggMC0xLjMxNi0uMzI5LTEuOTc0LS45MjFhMjAuNCAyMC40IDAgMCAxLTIuMzY4LTMuMDkyYTUxIDUxIDAgMCAxLTIuMDQtMy44ODJxLTcuNjk3IDkuMDgtMTkuMzQyIDkuMDc5Yy01LjUyNiAwLTkuOTM0LTEuNTc5LTEzLjE1OC00LjczN2MtMy4yMjMtMy4xNTgtNC44NjgtNy4zNjgtNC44NjgtMTIuNjMxYzAtNS41OTMgMS45NzQtMTAuMTMyIDUuOTg3LTEzLjU1M3M5LjM0Mi01LjEzMiAxNi4xMTgtNS4xMzJjMi4yMzcgMCA0LjU0LjE5OCA2Ljk3NC41MjdzNC45MzQuODU1IDcuNTY2IDEuNDQ3di00LjgwM2MwLTUtMS4wNTMtOC40ODctMy4wOTItMTAuNTI2Yy0yLjEwNi0yLjA0LTUuNjU4LTMuMDI2LTEwLjcyNC0zLjAyNmMtMi4zMDMgMC00LjY3MS4yNjMtNy4xMDUuODU1cy00LjgwMyAxLjMxNi03LjEwNiAyLjIzN2ExOSAxOSAwIDAgMS0yLjMwMi44NTVjLS40Ni4xMzItLjc5LjE5OC0xLjA1My4xOThjLS45MiAwLTEuMzgyLS42NTgtMS4zODItMi4wNHYtMy4yMjRjMC0xLjA1Mi4xMzItMS44NDIuNDYxLTIuMzAycy45MjEtLjkyMSAxLjg0Mi0xLjM4MnEzLjQ1NC0xLjc3NiA4LjI5LTIuOTZjMy4yMjMtLjg1NiA2LjY0NC0xLjI1IDEwLjI2My0xLjI1YzcuODI5IDAgMTMuNTUyIDEuNzc2IDE3LjIzNyA1LjMyOGMzLjYxOCAzLjU1MyA1LjQ2IDguOTQ4IDUuNDYgMTYuMTg1djIxLjMxNnptLTI2LjcxIDEwYzIuMTcgMCA0LjQwNy0uMzk1IDYuNzc2LTEuMTg1YzIuMzY4LS43ODkgNC40NzMtMi4yMzcgNi4yNS00LjIxYzEuMDUyLTEuMjUgMS44NDItMi42MzIgMi4yMzYtNC4yMTFzLjY1OC0zLjQ4Ny42NTgtNS43MjN2LTIuNzY0YTU1IDU1IDAgMCAwLTYuMDUyLTEuMTE4YTUwIDUwIDAgMCAwLTYuMTg1LS4zOTVjLTQuNDA4IDAtNy42MzEuODU2LTkuODAyIDIuNjMycy0zLjIyNCA0LjI3Ni0zLjIyNCA3LjU2NmMwIDMuMDkyLjc5IDUuMzk0IDIuNDM0IDYuOTczYzEuNTggMS42NDUgMy44ODIgMi40MzUgNi45MDggMi40MzVtNTIuODI4IDcuMTA1Yy0xLjE4NCAwLTEuOTc0LS4xOTgtMi41LS42NThjLS41MjYtLjM5NS0uOTg3LTEuMzE2LTEuMzgxLTIuNTY2bC0xNS40Ni01MC44NTVjLS4zOTYtMS4zMTYtLjU5My0yLjE3MS0uNTkzLTIuNjMyYzAtMS4wNTIuNTI2LTEuNjQ1IDEuNTc5LTEuNjQ1aDYuNDQ3YzEuMjUgMCAyLjEwNi4xOTggMi41NjYuNjU4Yy41MjYuMzk1LjkyMSAxLjMxNiAxLjMxNiAyLjU2NmwxMS4wNTIgNDMuNTUzbDEwLjI2NC00My41NTNjLjMyOS0xLjMxNi43MjMtMi4xNyAxLjI1LTIuNTY2Yy41MjYtLjM5NCAxLjQ0Ny0uNjU3IDIuNjMxLS42NTdoNS4yNjNjMS4yNSAwIDIuMTA2LjE5NyAyLjYzMi42NTdjLjUyNi4zOTUuOTg3IDEuMzE2IDEuMjUgMi41NjZsMTAuMzk1IDQ0LjA3OWwxMS4zODEtNDQuMDc5Yy4zOTUtMS4zMTYuODU2LTIuMTcgMS4zMTYtMi41NjZjLjUyNi0uMzk0IDEuMzgyLS42NTcgMi41NjYtLjY1N2g2LjExOGMxLjA1MyAwIDEuNjQ1LjUyNiAxLjY0NSAxLjY0NGMwIC4zMy0uMDY2LjY1OC0uMTMyIDEuMDUzYy0uMDY1LjM5NS0uMTk3LjkyLS40NiAxLjY0NWwtMTUuODU1IDUwLjg1NXEtLjU5MyAxLjk3NC0xLjM4MiAyLjU2NmMtLjUyNi4zOTQtMS4zODIuNjU4LTIuNS42NThoLTUuNjU4Yy0xLjI1IDAtMi4xMDUtLjE5OC0yLjYzMS0uNjU4Yy0uNTI3LS40NjEtLjk4Ny0xLjMxNi0xLjI1LTIuNjMybC0xMC4xOTgtNDIuNDM0bC0xMC4xMzEgNDIuMzY4Yy0uMzI5IDEuMzE2LS43MjQgMi4xNzEtMS4yNSAyLjYzMmMtLjUyNy40Ni0xLjQ0OC42NTgtMi42MzIuNjU4em04NC41NCAxLjc3NmMtMy40MjEgMC02Ljg0Mi0uMzk1LTEwLjEzMi0xLjE4NGMtMy4yODktLjc5LTUuODU1LTEuNjQ1LTcuNTY2LTIuNjMyYy0xLjA1Mi0uNTkyLTEuNzc2LTEuMjUtMi4wMzktMS44NDJhNC42NSA0LjY1IDAgMCAxLS4zOTUtMS44NDJ2LTMuMzU1YzAtMS4zODIuNTI2LTIuMDQgMS41MTMtMi4wNHEuNTkzIDAgMS4xODQuMTk4Yy4zOTUuMTMxLjk4Ny4zOTQgMS42NDUuNjU4YTM1LjggMzUuOCAwIDAgMCA3LjIzNyAyLjMwMmEzOS41IDM5LjUgMCAwIDAgNy44MjkuNzljNC4xNDUgMCA3LjM2OC0uNzI0IDkuNjA1LTIuMTcxYzIuMjM3LTEuNDQ4IDMuNDIxLTMuNTUzIDMuNDIxLTYuMjVjMC0xLjg0Mi0uNTkyLTMuMzU2LTEuNzc2LTQuNjA2cy0zLjQyMS0yLjM2OC02LjY0NS0zLjQyMWwtOS41MzktMi45NmMtNC44MDMtMS41MTMtOC4zNTYtMy43NS0xMC41MjctNi43MWMtMi4xNzEtMi44OTUtMy4yODktNi4xMi0zLjI4OS05LjU0cTAtNC4xNDQgMS43NzYtNy4zMDNjMS4xODQtMi4xMDUgMi43NjMtMy45NDcgNC43MzctNS4zOTRjMS45NzQtMS41MTQgNC4yMTEtMi42MzIgNi44NDItMy40MjJjMi42MzItLjc5IDUuMzk1LTEuMTE4IDguMjktMS4xMThjMS40NDcgMCAyLjk2LjA2NiA0LjQwOC4yNjNjMS41MTMuMTk3IDIuODk0LjQ2IDQuMjc2LjcyNGMxLjMxNi4zMjkgMi41NjYuNjU4IDMuNzUgMS4wNTNxMS43NzYuNTkxIDIuNzYzIDEuMTg0Yy45MjEuNTI2IDEuNTc5IDEuMDUyIDEuOTc0IDEuNjQ0cS41OTIuNzkuNTkyIDIuMTcydjMuMDkyYzAgMS4zODEtLjUyNiAyLjEwNS0xLjUxMyAyLjEwNWMtLjUyNyAwLTEuMzgyLS4yNjMtMi41LS43OXEtNS42MjUtMi41NjUtMTIuNjMyLTIuNTY1Yy0zLjc1IDAtNi43MS41OTItOC43NSAxLjg0MnMtMy4wOTIgMy4xNTgtMy4wOTIgNS44NTVjMCAxLjg0Mi42NTggMy40MjEgMS45NzQgNC42NzFzMy43NSAyLjUgNy4yMzcgMy42MThsOS4zNDIgMi45NmM0LjczNiAxLjUxNCA4LjE1OCAzLjYxOSAxMC4xOTcgNi4zMTdzMy4wMjYgNS43ODkgMy4wMjYgOS4yMWMwIDIuODI5LS41OTIgNS4zOTUtMS43MSA3LjYzMmMtMS4xODQgMi4yMzctMi43NjMgNC4yMS00LjgwMyA1Ljc4OWMtMi4wMzkgMS42NDUtNC40NzQgMi44MjktNy4zMDIgMy42ODVjLTIuOTYxLjkyMS02LjA1MyAxLjM4MS05LjQwOCAxLjM4MSIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIwNy44MzcgMTYyLjgxNmMtMjEuNjQ1IDE1Ljk4Ny01My4wOTIgMjQuNDc0LTgwLjEzMiAyNC40NzRjLTM3Ljg5NCAwLTcyLjA0LTE0LjAxNC05Ny44MjktMzcuMzAzYy0yLjA0LTEuODQyLS4xOTctNC4zNDIgMi4yMzctMi44OTVjMjcuODk1IDE2LjE4NCA2Mi4zMDMgMjUuOTg3IDk3Ljg5NSAyNS45ODdjMjQuMDEzIDAgNTAuMzk1LTUgNzQuNjcxLTE1LjI2M2MzLjYxOC0xLjY0NSA2LjcxIDIuMzY4IDMuMTU4IDUiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIxNi44NSAxNTIuNTUzYy0yLjc2My0zLjU1My0xOC4yODktMS43MTEtMjUuMzI5LS44NTZjLTIuMTA1LjI2NC0yLjQzNC0xLjU3OS0uNTI2LTIuOTZjMTIuMzY4LTguNjg0IDMyLjY5Ny02LjE4NCAzNS4wNjYtMy4yOWMyLjM2OCAyLjk2MS0uNjU4IDIzLjI5LTEyLjIzNyAzMy4wMjdjLTEuNzc3IDEuNTEzLTMuNDg3LjcyMy0yLjY5OC0xLjI1YzIuNjMyLTYuNTEzIDguNDg3LTIxLjE4NSA1LjcyNC0yNC42NzEiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=", - resources: 145, - added: "Jan 5, 2024", - }, - { - id: 10, - account: "aws", - provider_id: 740350143853, - connected: true, - group: "Operations", - scan_status: "active", - scan_status_time: "6 minutes, 50 seconds", - last_scan: "Jul 17, 2024 7:46am UTC", - next_scan: "Jul 18, 2024 7:46am UTC", - provider_img: - "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgdmlld0JveD0iMCAwIDI1NiAyNTYiPjxnIGZpbGw9Im5vbmUiPjxyZWN0IHdpZHRoPSIyNTYiIGhlaWdodD0iMjU2IiBmaWxsPSIjZjRmMmVkIiByeD0iNjAiLz48cGF0aCBmaWxsPSIjMjUyZjNlIiBkPSJNODQuNzQ1IDExMS45NjFjMCAyLjQzNC4yNjMgNC40MDcuNzIzIDUuODU1YTM1IDM1IDAgMCAwIDIuMTA2IDQuNzM3Yy4zMjkuNTI2LjQ2IDEuMDUyLjQ2IDEuNTEzYzAgLjY1OC0uMzk1IDEuMzE2LTEuMjUgMS45NzNsLTQuMTQ1IDIuNzY0Yy0uNTkyLjM5NC0xLjE4NC41OTItMS43MS41OTJjLS42NTggMC0xLjMxNi0uMzI5LTEuOTc0LS45MjFhMjAuNCAyMC40IDAgMCAxLTIuMzY4LTMuMDkyYTUxIDUxIDAgMCAxLTIuMDQtMy44ODJxLTcuNjk3IDkuMDgtMTkuMzQyIDkuMDc5Yy01LjUyNiAwLTkuOTM0LTEuNTc5LTEzLjE1OC00LjczN2MtMy4yMjMtMy4xNTgtNC44NjgtNy4zNjgtNC44NjgtMTIuNjMxYzAtNS41OTMgMS45NzQtMTAuMTMyIDUuOTg3LTEzLjU1M3M5LjM0Mi01LjEzMiAxNi4xMTgtNS4xMzJjMi4yMzcgMCA0LjU0LjE5OCA2Ljk3NC41MjdzNC45MzQuODU1IDcuNTY2IDEuNDQ3di00LjgwM2MwLTUtMS4wNTMtOC40ODctMy4wOTItMTAuNTI2Yy0yLjEwNi0yLjA0LTUuNjU4LTMuMDI2LTEwLjcyNC0zLjAyNmMtMi4zMDMgMC00LjY3MS4yNjMtNy4xMDUuODU1cy00LjgwMyAxLjMxNi03LjEwNiAyLjIzN2ExOSAxOSAwIDAgMS0yLjMwMi44NTVjLS40Ni4xMzItLjc5LjE5OC0xLjA1My4xOThjLS45MiAwLTEuMzgyLS42NTgtMS4zODItMi4wNHYtMy4yMjRjMC0xLjA1Mi4xMzItMS44NDIuNDYxLTIuMzAycy45MjEtLjkyMSAxLjg0Mi0xLjM4MnEzLjQ1NC0xLjc3NiA4LjI5LTIuOTZjMy4yMjMtLjg1NiA2LjY0NC0xLjI1IDEwLjI2My0xLjI1YzcuODI5IDAgMTMuNTUyIDEuNzc2IDE3LjIzNyA1LjMyOGMzLjYxOCAzLjU1MyA1LjQ2IDguOTQ4IDUuNDYgMTYuMTg1djIxLjMxNnptLTI2LjcxIDEwYzIuMTcgMCA0LjQwNy0uMzk1IDYuNzc2LTEuMTg1YzIuMzY4LS43ODkgNC40NzMtMi4yMzcgNi4yNS00LjIxYzEuMDUyLTEuMjUgMS44NDItMi42MzIgMi4yMzYtNC4yMTFzLjY1OC0zLjQ4Ny42NTgtNS43MjN2LTIuNzY0YTU1IDU1IDAgMCAwLTYuMDUyLTEuMTE4YTUwIDUwIDAgMCAwLTYuMTg1LS4zOTVjLTQuNDA4IDAtNy42MzEuODU2LTkuODAyIDIuNjMycy0zLjIyNCA0LjI3Ni0zLjIyNCA3LjU2NmMwIDMuMDkyLjc5IDUuMzk0IDIuNDM0IDYuOTczYzEuNTggMS42NDUgMy44ODIgMi40MzUgNi45MDggMi40MzVtNTIuODI4IDcuMTA1Yy0xLjE4NCAwLTEuOTc0LS4xOTgtMi41LS42NThjLS41MjYtLjM5NS0uOTg3LTEuMzE2LTEuMzgxLTIuNTY2bC0xNS40Ni01MC44NTVjLS4zOTYtMS4zMTYtLjU5My0yLjE3MS0uNTkzLTIuNjMyYzAtMS4wNTIuNTI2LTEuNjQ1IDEuNTc5LTEuNjQ1aDYuNDQ3YzEuMjUgMCAyLjEwNi4xOTggMi41NjYuNjU4Yy41MjYuMzk1LjkyMSAxLjMxNiAxLjMxNiAyLjU2NmwxMS4wNTIgNDMuNTUzbDEwLjI2NC00My41NTNjLjMyOS0xLjMxNi43MjMtMi4xNyAxLjI1LTIuNTY2Yy41MjYtLjM5NCAxLjQ0Ny0uNjU3IDIuNjMxLS42NTdoNS4yNjNjMS4yNSAwIDIuMTA2LjE5NyAyLjYzMi42NTdjLjUyNi4zOTUuOTg3IDEuMzE2IDEuMjUgMi41NjZsMTAuMzk1IDQ0LjA3OWwxMS4zODEtNDQuMDc5Yy4zOTUtMS4zMTYuODU2LTIuMTcgMS4zMTYtMi41NjZjLjUyNi0uMzk0IDEuMzgyLS42NTcgMi41NjYtLjY1N2g2LjExOGMxLjA1MyAwIDEuNjQ1LjUyNiAxLjY0NSAxLjY0NGMwIC4zMy0uMDY2LjY1OC0uMTMyIDEuMDUzYy0uMDY1LjM5NS0uMTk3LjkyLS40NiAxLjY0NWwtMTUuODU1IDUwLjg1NXEtLjU5MyAxLjk3NC0xLjM4MiAyLjU2NmMtLjUyNi4zOTQtMS4zODIuNjU4LTIuNS42NThoLTUuNjU4Yy0xLjI1IDAtMi4xMDUtLjE5OC0yLjYzMS0uNjU4Yy0uNTI3LS40NjEtLjk4Ny0xLjMxNi0xLjI1LTIuNjMybC0xMC4xOTgtNDIuNDM0bC0xMC4xMzEgNDIuMzY4Yy0uMzI5IDEuMzE2LS43MjQgMi4xNzEtMS4yNSAyLjYzMmMtLjUyNy40Ni0xLjQ0OC42NTgtMi42MzIuNjU4em04NC41NCAxLjc3NmMtMy40MjEgMC02Ljg0Mi0uMzk1LTEwLjEzMi0xLjE4NGMtMy4yODktLjc5LTUuODU1LTEuNjQ1LTcuNTY2LTIuNjMyYy0xLjA1Mi0uNTkyLTEuNzc2LTEuMjUtMi4wMzktMS44NDJhNC42NSA0LjY1IDAgMCAxLS4zOTUtMS44NDJ2LTMuMzU1YzAtMS4zODIuNTI2LTIuMDQgMS41MTMtMi4wNHEuNTkzIDAgMS4xODQuMTk4Yy4zOTUuMTMxLjk4Ny4zOTQgMS42NDUuNjU4YTM1LjggMzUuOCAwIDAgMCA3LjIzNyAyLjMwMmEzOS41IDM5LjUgMCAwIDAgNy44MjkuNzljNC4xNDUgMCA3LjM2OC0uNzI0IDkuNjA1LTIuMTcxYzIuMjM3LTEuNDQ4IDMuNDIxLTMuNTUzIDMuNDIxLTYuMjVjMC0xLjg0Mi0uNTkyLTMuMzU2LTEuNzc2LTQuNjA2cy0zLjQyMS0yLjM2OC02LjY0NS0zLjQyMWwtOS41MzktMi45NmMtNC44MDMtMS41MTMtOC4zNTYtMy43NS0xMC41MjctNi43MWMtMi4xNzEtMi44OTUtMy4yODktNi4xMi0zLjI4OS05LjU0cTAtNC4xNDQgMS43NzYtNy4zMDNjMS4xODQtMi4xMDUgMi43NjMtMy45NDcgNC43MzctNS4zOTRjMS45NzQtMS41MTQgNC4yMTEtMi42MzIgNi44NDItMy40MjJjMi42MzItLjc5IDUuMzk1LTEuMTE4IDguMjktMS4xMThjMS40NDcgMCAyLjk2LjA2NiA0LjQwOC4yNjNjMS41MTMuMTk3IDIuODk0LjQ2IDQuMjc2LjcyNGMxLjMxNi4zMjkgMi41NjYuNjU4IDMuNzUgMS4wNTNxMS43NzYuNTkxIDIuNzYzIDEuMTg0Yy45MjEuNTI2IDEuNTc5IDEuMDUyIDEuOTc0IDEuNjQ0cS41OTIuNzkuNTkyIDIuMTcydjMuMDkyYzAgMS4zODEtLjUyNiAyLjEwNS0xLjUxMyAyLjEwNWMtLjUyNyAwLTEuMzgyLS4yNjMtMi41LS43OXEtNS42MjUtMi41NjUtMTIuNjMyLTIuNTY1Yy0zLjc1IDAtNi43MS41OTItOC43NSAxLjg0MnMtMy4wOTIgMy4xNTgtMy4wOTIgNS44NTVjMCAxLjg0Mi42NTggMy40MjEgMS45NzQgNC42NzFzMy43NSAyLjUgNy4yMzcgMy42MThsOS4zNDIgMi45NmM0LjczNiAxLjUxNCA4LjE1OCAzLjYxOSAxMC4xOTcgNi4zMTdzMy4wMjYgNS43ODkgMy4wMjYgOS4yMWMwIDIuODI5LS41OTIgNS4zOTUtMS43MSA3LjYzMmMtMS4xODQgMi4yMzctMi43NjMgNC4yMS00LjgwMyA1Ljc4OWMtMi4wMzkgMS42NDUtNC40NzQgMi44MjktNy4zMDIgMy42ODVjLTIuOTYxLjkyMS02LjA1MyAxLjM4MS05LjQwOCAxLjM4MSIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIwNy44MzcgMTYyLjgxNmMtMjEuNjQ1IDE1Ljk4Ny01My4wOTIgMjQuNDc0LTgwLjEzMiAyNC40NzRjLTM3Ljg5NCAwLTcyLjA0LTE0LjAxNC05Ny44MjktMzcuMzAzYy0yLjA0LTEuODQyLS4xOTctNC4zNDIgMi4yMzctMi44OTVjMjcuODk1IDE2LjE4NCA2Mi4zMDMgMjUuOTg3IDk3Ljg5NSAyNS45ODdjMjQuMDEzIDAgNTAuMzk1LTUgNzQuNjcxLTE1LjI2M2MzLjYxOC0xLjY0NSA2LjcxIDIuMzY4IDMuMTU4IDUiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIxNi44NSAxNTIuNTUzYy0yLjc2My0zLjU1My0xOC4yODktMS43MTEtMjUuMzI5LS44NTZjLTIuMTA1LjI2NC0yLjQzNC0xLjU3OS0uNTI2LTIuOTZjMTIuMzY4LTguNjg0IDMyLjY5Ny02LjE4NCAzNS4wNjYtMy4yOWMyLjM2OCAyLjk2MS0uNjU4IDIzLjI5LTEyLjIzNyAzMy4wMjdjLTEuNzc3IDEuNTEzLTMuNDg3LjcyMy0yLjY5OC0xLjI1YzIuNjMyLTYuNTEzIDguNDg3LTIxLjE4NSA1LjcyNC0yNC42NzEiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=", - resources: 132, - added: "Mar 8, 2024", - }, - { - id: 11, - account: "aws", - provider_id: 740350143854, - connected: true, - group: "Development", - scan_status: "active", - scan_status_time: "4 minutes, 15 seconds", - last_scan: "Jul 17, 2024 6:46am UTC", - next_scan: "Jul 18, 2024 6:46am UTC", - provider_img: - "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgdmlld0JveD0iMCAwIDI1NiAyNTYiPjxnIGZpbGw9Im5vbmUiPjxyZWN0IHdpZHRoPSIyNTYiIGhlaWdodD0iMjU2IiBmaWxsPSIjZjRmMmVkIiByeD0iNjAiLz48cGF0aCBmaWxsPSIjMjUyZjNlIiBkPSJNODQuNzQ1IDExMS45NjFjMCAyLjQzNC4yNjMgNC40MDcuNzIzIDUuODU1YTM1IDM1IDAgMCAwIDIuMTA2IDQuNzM3Yy4zMjkuNTI2LjQ2IDEuMDUyLjQ2IDEuNTEzYzAgLjY1OC0uMzk1IDEuMzE2LTEuMjUgMS45NzNsLTQuMTQ1IDIuNzY0Yy0uNTkyLjM5NC0xLjE4NC41OTItMS43MS41OTJjLS42NTggMC0xLjMxNi0uMzI5LTEuOTc0LS45MjFhMjAuNCAyMC40IDAgMCAxLTIuMzY4LTMuMDkyYTUxIDUxIDAgMCAxLTIuMDQtMy44ODJxLTcuNjk3IDkuMDgtMTkuMzQyIDkuMDc5Yy01LjUyNiAwLTkuOTM0LTEuNTc5LTEzLjE1OC00LjczN2MtMy4yMjMtMy4xNTgtNC44NjgtNy4zNjgtNC44NjgtMTIuNjMxYzAtNS41OTMgMS45NzQtMTAuMTMyIDUuOTg3LTEzLjU1M3M5LjM0Mi01LjEzMiAxNi4xMTgtNS4xMzJjMi4yMzcgMCA0LjU0LjE5OCA2Ljk3NC41MjdzNC45MzQuODU1IDcuNTY2IDEuNDQ3di00LjgwM2MwLTUtMS4wNTMtOC40ODctMy4wOTItMTAuNTI2Yy0yLjEwNi0yLjA0LTUuNjU4LTMuMDI2LTEwLjcyNC0zLjAyNmMtMi4zMDMgMC00LjY3MS4yNjMtNy4xMDUuODU1cy00LjgwMyAxLjMxNi03LjEwNiAyLjIzN2ExOSAxOSAwIDAgMS0yLjMwMi44NTVjLS40Ni4xMzItLjc5LjE5OC0xLjA1My4xOThjLS45MiAwLTEuMzgyLS42NTgtMS4zODItMi4wNHYtMy4yMjRjMC0xLjA1Mi4xMzItMS44NDIuNDYxLTIuMzAycy45MjEtLjkyMSAxLjg0Mi0xLjM4MnEzLjQ1NC0xLjc3NiA4LjI5LTIuOTZjMy4yMjMtLjg1NiA2LjY0NC0xLjI1IDEwLjI2My0xLjI1YzcuODI5IDAgMTMuNTUyIDEuNzc2IDE3LjIzNyA1LjMyOGMzLjYxOCAzLjU1MyA1LjQ2IDguOTQ4IDUuNDYgMTYuMTg1djIxLjMxNnptLTI2LjcxIDEwYzIuMTcgMCA0LjQwNy0uMzk1IDYuNzc2LTEuMTg1YzIuMzY4LS43ODkgNC40NzMtMi4yMzcgNi4yNS00LjIxYzEuMDUyLTEuMjUgMS44NDItMi42MzIgMi4yMzYtNC4yMTFzLjY1OC0zLjQ4Ny42NTgtNS43MjN2LTIuNzY0YTU1IDU1IDAgMCAwLTYuMDUyLTEuMTE4YTUwIDUwIDAgMCAwLTYuMTg1LS4zOTVjLTQuNDA4IDAtNy42MzEuODU2LTkuODAyIDIuNjMycy0zLjIyNCA0LjI3Ni0zLjIyNCA3LjU2NmMwIDMuMDkyLjc5IDUuMzk0IDIuNDM0IDYuOTczYzEuNTggMS42NDUgMy44ODIgMi40MzUgNi45MDggMi40MzVtNTIuODI4IDcuMTA1Yy0xLjE4NCAwLTEuOTc0LS4xOTgtMi41LS42NThjLS41MjYtLjM5NS0uOTg3LTEuMzE2LTEuMzgxLTIuNTY2bC0xNS40Ni01MC44NTVjLS4zOTYtMS4zMTYtLjU5My0yLjE3MS0uNTkzLTIuNjMyYzAtMS4wNTIuNTI2LTEuNjQ1IDEuNTc5LTEuNjQ1aDYuNDQ3YzEuMjUgMCAyLjEwNi4xOTggMi41NjYuNjU4Yy41MjYuMzk1LjkyMSAxLjMxNiAxLjMxNiAyLjU2NmwxMS4wNTIgNDMuNTUzbDEwLjI2NC00My41NTNjLjMyOS0xLjMxNi43MjMtMi4xNyAxLjI1LTIuNTY2Yy41MjYtLjM5NCAxLjQ0Ny0uNjU3IDIuNjMxLS42NTdoNS4yNjNjMS4yNSAwIDIuMTA2LjE5NyAyLjYzMi42NTdjLjUyNi4zOTUuOTg3IDEuMzE2IDEuMjUgMi41NjZsMTAuMzk1IDQ0LjA3OWwxMS4zODEtNDQuMDc5Yy4zOTUtMS4zMTYuODU2LTIuMTcgMS4zMTYtMi41NjZjLjUyNi0uMzk0IDEuMzgyLS42NTcgMi41NjYtLjY1N2g2LjExOGMxLjA1MyAwIDEuNjQ1LjUyNiAxLjY0NSAxLjY0NGMwIC4zMy0uMDY2LjY1OC0uMTMyIDEuMDUzYy0uMDY1LjM5NS0uMTk3LjkyLS40NiAxLjY0NWwtMTUuODU1IDUwLjg1NXEtLjU5MyAxLjk3NC0xLjM4MiAyLjU2NmMtLjUyNi4zOTQtMS4zODIuNjU4LTIuNS42NThoLTUuNjU4Yy0xLjI1IDAtMi4xMDUtLjE5OC0yLjYzMS0uNjU4Yy0uNTI3LS40NjEtLjk4Ny0xLjMxNi0xLjI1LTIuNjMybC0xMC4xOTgtNDIuNDM0bC0xMC4xMzEgNDIuMzY4Yy0uMzI5IDEuMzE2LS43MjQgMi4xNzEtMS4yNSAyLjYzMmMtLjUyNy40Ni0xLjQ0OC42NTgtMi42MzIuNjU4em04NC41NCAxLjc3NmMtMy40MjEgMC02Ljg0Mi0uMzk1LTEwLjEzMi0xLjE4NGMtMy4yODktLjc5LTUuODU1LTEuNjQ1LTcuNTY2LTIuNjMyYy0xLjA1Mi0uNTkyLTEuNzc2LTEuMjUtMi4wMzktMS44NDJhNC42NSA0LjY1IDAgMCAxLS4zOTUtMS44NDJ2LTMuMzU1YzAtMS4zODIuNTI2LTIuMDQgMS41MTMtMi4wNHEuNTkzIDAgMS4xODQuMTk4Yy4zOTUuMTMxLjk4Ny4zOTQgMS42NDUuNjU4YTM1LjggMzUuOCAwIDAgMCA3LjIzNyAyLjMwMmEzOS41IDM5LjUgMCAwIDAgNy44MjkuNzljNC4xNDUgMCA3LjM2OC0uNzI0IDkuNjA1LTIuMTcxYzIuMjM3LTEuNDQ4IDMuNDIxLTMuNTUzIDMuNDIxLTYuMjVjMC0xLjg0Mi0uNTkyLTMuMzU2LTEuNzc2LTQuNjA2cy0zLjQyMS0yLjM2OC02LjY0NS0zLjQyMWwtOS41MzktMi45NmMtNC44MDMtMS41MTMtOC4zNTYtMy43NS0xMC41MjctNi43MWMtMi4xNzEtMi44OTUtMy4yODktNi4xMi0zLjI4OS05LjU0cTAtNC4xNDQgMS43NzYtNy4zMDNjMS4xODQtMi4xMDUgMi43NjMtMy45NDcgNC43MzctNS4zOTRjMS45NzQtMS41MTQgNC4yMTEtMi42MzIgNi44NDItMy40MjJjMi42MzItLjc5IDUuMzk1LTEuMTE4IDguMjktMS4xMThjMS40NDcgMCAyLjk2LjA2NiA0LjQwOC4yNjNjMS41MTMuMTk3IDIuODk0LjQ2IDQuMjc2LjcyNGMxLjMxNi4zMjkgMi41NjYuNjU4IDMuNzUgMS4wNTNxMS43NzYuNTkxIDIuNzYzIDEuMTg0Yy45MjEuNTI2IDEuNTc5IDEuMDUyIDEuOTc0IDEuNjQ0cS41OTIuNzkuNTkyIDIuMTcydjMuMDkyYzAgMS4zODEtLjUyNiAyLjEwNS0xLjUxMyAyLjEwNWMtLjUyNyAwLTEuMzgyLS4yNjMtMi41LS43OXEtNS42MjUtMi41NjUtMTIuNjMyLTIuNTY1Yy0zLjc1IDAtNi43MS41OTItOC43NSAxLjg0MnMtMy4wOTIgMy4xNTgtMy4wOTIgNS44NTVjMCAxLjg0Mi42NTggMy40MjEgMS45NzQgNC42NzFzMy43NSAyLjUgNy4yMzcgMy42MThsOS4zNDIgMi45NmM0LjczNiAxLjUxNCA4LjE1OCAzLjYxOSAxMC4xOTcgNi4zMTdzMy4wMjYgNS43ODkgMy4wMjYgOS4yMWMwIDIuODI5LS41OTIgNS4zOTUtMS43MSA3LjYzMmMtMS4xODQgMi4yMzctMi43NjMgNC4yMS00LjgwMyA1Ljc4OWMtMi4wMzkgMS42NDUtNC40NzQgMi44MjktNy4zMDIgMy42ODVjLTIuOTYxLjkyMS02LjA1MyAxLjM4MS05LjQwOCAxLjM4MSIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIwNy44MzcgMTYyLjgxNmMtMjEuNjQ1IDE1Ljk4Ny01My4wOTIgMjQuNDc0LTgwLjEzMiAyNC40NzRjLTM3Ljg5NCAwLTcyLjA0LTE0LjAxNC05Ny44MjktMzcuMzAzYy0yLjA0LTEuODQyLS4xOTctNC4zNDIgMi4yMzctMi44OTVjMjcuODk1IDE2LjE4NCA2Mi4zMDMgMjUuOTg3IDk3Ljg5NSAyNS45ODdjMjQuMDEzIDAgNTAuMzk1LTUgNzQuNjcxLTE1LjI2M2MzLjYxOC0xLjY0NSA2LjcxIDIuMzY4IDMuMTU4IDUiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIxNi44NSAxNTIuNTUzYy0yLjc2My0zLjU1My0xOC4yODktMS43MTEtMjUuMzI5LS44NTZjLTIuMTA1LjI2NC0yLjQzNC0xLjU3OS0uNTI2LTIuOTZjMTIuMzY4LTguNjg0IDMyLjY5Ny02LjE4NCAzNS4wNjYtMy4yOWMyLjM2OCAyLjk2MS0uNjU4IDIzLjI5LTEyLjIzNyAzMy4wMjdjLTEuNzc3IDEuNTEzLTMuNDg3LjcyMy0yLjY5OC0xLjI1YzIuNjMyLTYuNTEzIDguNDg3LTIxLjE4NSA1LjcyNC0yNC42NzEiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=", - resources: 121, - added: "Apr 21, 2024", - }, - { - id: 12, - account: "aws", - provider_id: 740350143855, - connected: false, - group: "Product", - scan_status: "paused", - scan_status_time: "11 minutes, 40 seconds", - last_scan: "Jul 17, 2024 5:46am UTC", - next_scan: "Jul 18, 2024 5:46am UTC", - provider_img: - "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgdmlld0JveD0iMCAwIDI1NiAyNTYiPjxnIGZpbGw9Im5vbmUiPjxyZWN0IHdpZHRoPSIyNTYiIGhlaWdodD0iMjU2IiBmaWxsPSIjZjRmMmVkIiByeD0iNjAiLz48cGF0aCBmaWxsPSIjMjUyZjNlIiBkPSJNODQuNzQ1IDExMS45NjFjMCAyLjQzNC4yNjMgNC40MDcuNzIzIDUuODU1YTM1IDM1IDAgMCAwIDIuMTA2IDQuNzM3Yy4zMjkuNTI2LjQ2IDEuMDUyLjQ2IDEuNTEzYzAgLjY1OC0uMzk1IDEuMzE2LTEuMjUgMS45NzNsLTQuMTQ1IDIuNzY0Yy0uNTkyLjM5NC0xLjE4NC41OTItMS43MS41OTJjLS42NTggMC0xLjMxNi0uMzI5LTEuOTc0LS45MjFhMjAuNCAyMC40IDAgMCAxLTIuMzY4LTMuMDkyYTUxIDUxIDAgMCAxLTIuMDQtMy44ODJxLTcuNjk3IDkuMDgtMTkuMzQyIDkuMDc5Yy01LjUyNiAwLTkuOTM0LTEuNTc5LTEzLjE1OC00LjczN2MtMy4yMjMtMy4xNTgtNC44NjgtNy4zNjgtNC44NjgtMTIuNjMxYzAtNS41OTMgMS45NzQtMTAuMTMyIDUuOTg3LTEzLjU1M3M5LjM0Mi01LjEzMiAxNi4xMTgtNS4xMzJjMi4yMzcgMCA0LjU0LjE5OCA2Ljk3NC41MjdzNC45MzQuODU1IDcuNTY2IDEuNDQ3di00LjgwM2MwLTUtMS4wNTMtOC40ODctMy4wOTItMTAuNTI2Yy0yLjEwNi0yLjA0LTUuNjU4LTMuMDI2LTEwLjcyNC0zLjAyNmMtMi4zMDMgMC00LjY3MS4yNjMtNy4xMDUuODU1cy00LjgwMyAxLjMxNi03LjEwNiAyLjIzN2ExOSAxOSAwIDAgMS0yLjMwMi44NTVjLS40Ni4xMzItLjc5LjE5OC0xLjA1My4xOThjLS45MiAwLTEuMzgyLS42NTgtMS4zODItMi4wNHYtMy4yMjRjMC0xLjA1Mi4xMzItMS44NDIuNDYxLTIuMzAycy45MjEtLjkyMSAxLjg0Mi0xLjM4MnEzLjQ1NC0xLjc3NiA4LjI5LTIuOTZjMy4yMjMtLjg1NiA2LjY0NC0xLjI1IDEwLjI2My0xLjI1YzcuODI5IDAgMTMuNTUyIDEuNzc2IDE3LjIzNyA1LjMyOGMzLjYxOCAzLjU1MyA1LjQ2IDguOTQ4IDUuNDYgMTYuMTg1djIxLjMxNnptLTI2LjcxIDEwYzIuMTcgMCA0LjQwNy0uMzk1IDYuNzc2LTEuMTg1YzIuMzY4LS43ODkgNC40NzMtMi4yMzcgNi4yNS00LjIxYzEuMDUyLTEuMjUgMS44NDItMi42MzIgMi4yMzYtNC4yMTFzLjY1OC0zLjQ4Ny42NTgtNS43MjN2LTIuNzY0YTU1IDU1IDAgMCAwLTYuMDUyLTEuMTE4YTUwIDUwIDAgMCAwLTYuMTg1LS4zOTVjLTQuNDA4IDAtNy42MzEuODU2LTkuODAyIDIuNjMycy0zLjIyNCA0LjI3Ni0zLjIyNCA3LjU2NmMwIDMuMDkyLjc5IDUuMzk0IDIuNDM0IDYuOTczYzEuNTggMS42NDUgMy44ODIgMi40MzUgNi45MDggMi40MzVtNTIuODI4IDcuMTA1Yy0xLjE4NCAwLTEuOTc0LS4xOTgtMi41LS42NThjLS41MjYtLjM5NS0uOTg3LTEuMzE2LTEuMzgxLTIuNTY2bC0xNS40Ni01MC44NTVjLS4zOTYtMS4zMTYtLjU5My0yLjE3MS0uNTkzLTIuNjMyYzAtMS4wNTIuNTI2LTEuNjQ1IDEuNTc5LTEuNjQ1aDYuNDQ3YzEuMjUgMCAyLjEwNi4xOTggMi41NjYuNjU4Yy41MjYuMzk1LjkyMSAxLjMxNiAxLjMxNiAyLjU2NmwxMS4wNTIgNDMuNTUzbDEwLjI2NC00My41NTNjLjMyOS0xLjMxNi43MjMtMi4xNyAxLjI1LTIuNTY2Yy41MjYtLjM5NCAxLjQ0Ny0uNjU3IDIuNjMxLS42NTdoNS4yNjNjMS4yNSAwIDIuMTA2LjE5NyAyLjYzMi42NTdjLjUyNi4zOTUuOTg3IDEuMzE2IDEuMjUgMi41NjZsMTAuMzk1IDQ0LjA3OWwxMS4zODEtNDQuMDc5Yy4zOTUtMS4zMTYuODU2LTIuMTcgMS4zMTYtMi41NjZjLjUyNi0uMzk0IDEuMzgyLS42NTcgMi41NjYtLjY1N2g2LjExOGMxLjA1MyAwIDEuNjQ1LjUyNiAxLjY0NSAxLjY0NGMwIC4zMy0uMDY2LjY1OC0uMTMyIDEuMDUzYy0uMDY1LjM5NS0uMTk3LjkyLS40NiAxLjY0NWwtMTUuODU1IDUwLjg1NXEtLjU5MyAxLjk3NC0xLjM4MiAyLjU2NmMtLjUyNi4zOTQtMS4zODIuNjU4LTIuNS42NThoLTUuNjU4Yy0xLjI1IDAtMi4xMDUtLjE5OC0yLjYzMS0uNjU4Yy0uNTI3LS40NjEtLjk4Ny0xLjMxNi0xLjI1LTIuNjMybC0xMC4xOTgtNDIuNDM0bC0xMC4xMzEgNDIuMzY4Yy0uMzI5IDEuMzE2LS43MjQgMi4xNzEtMS4yNSAyLjYzMmMtLjUyNy40Ni0xLjQ0OC42NTgtMi42MzIuNjU4em04NC41NCAxLjc3NmMtMy40MjEgMC02Ljg0Mi0uMzk1LTEwLjEzMi0xLjE4NGMtMy4yODktLjc5LTUuODU1LTEuNjQ1LTcuNTY2LTIuNjMyYy0xLjA1Mi0uNTkyLTEuNzc2LTEuMjUtMi4wMzktMS44NDJhNC42NSA0LjY1IDAgMCAxLS4zOTUtMS44NDJ2LTMuMzU1YzAtMS4zODIuNTI2LTIuMDQgMS41MTMtMi4wNHEuNTkzIDAgMS4xODQuMTk4Yy4zOTUuMTMxLjk4Ny4zOTQgMS42NDUuNjU4YTM1LjggMzUuOCAwIDAgMCA3LjIzNyAyLjMwMmEzOS41IDM5LjUgMCAwIDAgNy44MjkuNzljNC4xNDUgMCA3LjM2OC0uNzI0IDkuNjA1LTIuMTcxYzIuMjM3LTEuNDQ4IDMuNDIxLTMuNTUzIDMuNDIxLTYuMjVjMC0xLjg0Mi0uNTkyLTMuMzU2LTEuNzc2LTQuNjA2cy0zLjQyMS0yLjM2OC02LjY0NS0zLjQyMWwtOS41MzktMi45NmMtNC44MDMtMS41MTMtOC4zNTYtMy43NS0xMC41MjctNi43MWMtMi4xNzEtMi44OTUtMy4yODktNi4xMi0zLjI4OS05LjU0cTAtNC4xNDQgMS43NzYtNy4zMDNjMS4xODQtMi4xMDUgMi43NjMtMy45NDcgNC43MzctNS4zOTRjMS45NzQtMS41MTQgNC4yMTEtMi42MzIgNi44NDItMy40MjJjMi42MzItLjc5IDUuMzk1LTEuMTE4IDguMjktMS4xMThjMS40NDcgMCAyLjk2LjA2NiA0LjQwOC4yNjNjMS41MTMuMTk3IDIuODk0LjQ2IDQuMjc2LjcyNGMxLjMxNi4zMjkgMi41NjYuNjU4IDMuNzUgMS4wNTNxMS43NzYuNTkxIDIuNzYzIDEuMTg0Yy45MjEuNTI2IDEuNTc5IDEuMDUyIDEuOTc0IDEuNjQ0cS41OTIuNzkuNTkyIDIuMTcydjMuMDkyYzAgMS4zODEtLjUyNiAyLjEwNS0xLjUxMyAyLjEwNWMtLjUyNyAwLTEuMzgyLS4yNjMtMi41LS43OXEtNS42MjUtMi41NjUtMTIuNjMyLTIuNTY1Yy0zLjc1IDAtNi43MS41OTItOC43NSAxLjg0MnMtMy4wOTIgMy4xNTgtMy4wOTIgNS44NTVjMCAxLjg0Mi42NTggMy40MjEgMS45NzQgNC42NzFzMy43NSAyLjUgNy4yMzcgMy42MThsOS4zNDIgMi45NmM0LjczNiAxLjUxNCA4LjE1OCAzLjYxOSAxMC4xOTcgNi4zMTdzMy4wMjYgNS43ODkgMy4wMjYgOS4yMWMwIDIuODI5LS41OTIgNS4zOTUtMS43MSA3LjYzMmMtMS4xODQgMi4yMzctMi43NjMgNC4yMS00LjgwMyA1Ljc4OWMtMi4wMzkgMS42NDUtNC40NzQgMi44MjktNy4zMDIgMy42ODVjLTIuOTYxLjkyMS02LjA1MyAxLjM4MS05LjQwOCAxLjM4MSIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIwNy44MzcgMTYyLjgxNmMtMjEuNjQ1IDE1Ljk4Ny01My4wOTIgMjQuNDc0LTgwLjEzMiAyNC40NzRjLTM3Ljg5NCAwLTcyLjA0LTE0LjAxNC05Ny44MjktMzcuMzAzYy0yLjA0LTEuODQyLS4xOTctNC4zNDIgMi4yMzctMi44OTVjMjcuODk1IDE2LjE4NCA2Mi4zMDMgMjUuOTg3IDk3Ljg5NSAyNS45ODdjMjQuMDEzIDAgNTAuMzk1LTUgNzQuNjcxLTE1LjI2M2MzLjYxOC0xLjY0NSA2LjcxIDIuMzY4IDMuMTU4IDUiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIxNi44NSAxNTIuNTUzYy0yLjc2My0zLjU1My0xOC4yODktMS43MTEtMjUuMzI5LS44NTZjLTIuMTA1LjI2NC0yLjQzNC0xLjU3OS0uNTI2LTIuOTZjMTIuMzY4LTguNjg0IDMyLjY5Ny02LjE4NCAzNS4wNjYtMy4yOWMyLjM2OCAyLjk2MS0uNjU4IDIzLjI5LTEyLjIzNyAzMy4wMjdjLTEuNzc3IDEuNTEzLTMuNDg3LjcyMy0yLjY5OC0xLjI1YzIuNjMyLTYuNTEzIDguNDg3LTIxLjE4NSA1LjcyNC0yNC42NzEiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=", - resources: 102, - added: "Feb 11, 2024", - }, - { - id: 13, - account: "aws", - provider_id: 740350143856, - connected: true, - group: "Security", - scan_status: "active", - scan_status_time: "9 minutes, 50 seconds", - last_scan: "Jul 17, 2024 4:46am UTC", - next_scan: "Jul 18, 2024 4:46am UTC", - provider_img: - "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgdmlld0JveD0iMCAwIDI1NiAyNTYiPjxnIGZpbGw9Im5vbmUiPjxyZWN0IHdpZHRoPSIyNTYiIGhlaWdodD0iMjU2IiBmaWxsPSIjZjRmMmVkIiByeD0iNjAiLz48cGF0aCBmaWxsPSIjMjUyZjNlIiBkPSJNODQuNzQ1IDExMS45NjFjMCAyLjQzNC4yNjMgNC40MDcuNzIzIDUuODU1YTM1IDM1IDAgMCAwIDIuMTA2IDQuNzM3Yy4zMjkuNTI2LjQ2IDEuMDUyLjQ2IDEuNTEzYzAgLjY1OC0uMzk1IDEuMzE2LTEuMjUgMS45NzNsLTQuMTQ1IDIuNzY0Yy0uNTkyLjM5NC0xLjE4NC41OTItMS43MS41OTJjLS42NTggMC0xLjMxNi0uMzI5LTEuOTc0LS45MjFhMjAuNCAyMC40IDAgMCAxLTIuMzY4LTMuMDkyYTUxIDUxIDAgMCAxLTIuMDQtMy44ODJxLTcuNjk3IDkuMDgtMTkuMzQyIDkuMDc5Yy01LjUyNiAwLTkuOTM0LTEuNTc5LTEzLjE1OC00LjczN2MtMy4yMjMtMy4xNTgtNC44NjgtNy4zNjgtNC44NjgtMTIuNjMxYzAtNS41OTMgMS45NzQtMTAuMTMyIDUuOTg3LTEzLjU1M3M5LjM0Mi01LjEzMiAxNi4xMTgtNS4xMzJjMi4yMzcgMCA0LjU0LjE5OCA2Ljk3NC41MjdzNC45MzQuODU1IDcuNTY2IDEuNDQ3di00LjgwM2MwLTUtMS4wNTMtOC40ODctMy4wOTItMTAuNTI2Yy0yLjEwNi0yLjA0LTUuNjU4LTMuMDI2LTEwLjcyNC0zLjAyNmMtMi4zMDMgMC00LjY3MS4yNjMtNy4xMDUuODU1cy00LjgwMyAxLjMxNi03LjEwNiAyLjIzN2ExOSAxOSAwIDAgMS0yLjMwMi44NTVjLS40Ni4xMzItLjc5LjE5OC0xLjA1My4xOThjLS45MiAwLTEuMzgyLS42NTgtMS4zODItMi4wNHYtMy4yMjRjMC0xLjA1Mi4xMzItMS44NDIuNDYxLTIuMzAycy45MjEtLjkyMSAxLjg0Mi0xLjM4MnEzLjQ1NC0xLjc3NiA4LjI5LTIuOTZjMy4yMjMtLjg1NiA2LjY0NC0xLjI1IDEwLjI2My0xLjI1YzcuODI5IDAgMTMuNTUyIDEuNzc2IDE3LjIzNyA1LjMyOGMzLjYxOCAzLjU1MyA1LjQ2IDguOTQ4IDUuNDYgMTYuMTg1djIxLjMxNnptLTI2LjcxIDEwYzIuMTcgMCA0LjQwNy0uMzk1IDYuNzc2LTEuMTg1YzIuMzY4LS43ODkgNC40NzMtMi4yMzcgNi4yNS00LjIxYzEuMDUyLTEuMjUgMS44NDItMi42MzIgMi4yMzYtNC4yMTFzLjY1OC0zLjQ4Ny42NTgtNS43MjN2LTIuNzY0YTU1IDU1IDAgMCAwLTYuMDUyLTEuMTE4YTUwIDUwIDAgMCAwLTYuMTg1LS4zOTVjLTQuNDA4IDAtNy42MzEuODU2LTkuODAyIDIuNjMycy0zLjIyNCA0LjI3Ni0zLjIyNCA3LjU2NmMwIDMuMDkyLjc5IDUuMzk0IDIuNDM0IDYuOTczYzEuNTggMS42NDUgMy44ODIgMi40MzUgNi45MDggMi40MzVtNTIuODI4IDcuMTA1Yy0xLjE4NCAwLTEuOTc0LS4xOTgtMi41LS42NThjLS41MjYtLjM5NS0uOTg3LTEuMzE2LTEuMzgxLTIuNTY2bC0xNS40Ni01MC44NTVjLS4zOTYtMS4zMTYtLjU5My0yLjE3MS0uNTkzLTIuNjMyYzAtMS4wNTIuNTI2LTEuNjQ1IDEuNTc5LTEuNjQ1aDYuNDQ3YzEuMjUgMCAyLjEwNi4xOTggMi41NjYuNjU4Yy41MjYuMzk1LjkyMSAxLjMxNiAxLjMxNiAyLjU2NmwxMS4wNTIgNDMuNTUzbDEwLjI2NC00My41NTNjLjMyOS0xLjMxNi43MjMtMi4xNyAxLjI1LTIuNTY2Yy41MjYtLjM5NCAxLjQ0Ny0uNjU3IDIuNjMxLS42NTdoNS4yNjNjMS4yNSAwIDIuMTA2LjE5NyAyLjYzMi42NTdjLjUyNi4zOTUuOTg3IDEuMzE2IDEuMjUgMi41NjZsMTAuMzk1IDQ0LjA3OWwxMS4zODEtNDQuMDc5Yy4zOTUtMS4zMTYuODU2LTIuMTcgMS4zMTYtMi41NjZjLjUyNi0uMzk0IDEuMzgyLS42NTcgMi41NjYtLjY1N2g2LjExOGMxLjA1MyAwIDEuNjQ1LjUyNiAxLjY0NSAxLjY0NGMwIC4zMy0uMDY2LjY1OC0uMTMyIDEuMDUzYy0uMDY1LjM5NS0uMTk3LjkyLS40NiAxLjY0NWwtMTUuODU1IDUwLjg1NXEtLjU5MyAxLjk3NC0xLjM4MiAyLjU2NmMtLjUyNi4zOTQtMS4zODIuNjU4LTIuNS42NThoLTUuNjU4Yy0xLjI1IDAtMi4xMDUtLjE5OC0yLjYzMS0uNjU4Yy0uNTI3LS40NjEtLjk4Ny0xLjMxNi0xLjI1LTIuNjMybC0xMC4xOTgtNDIuNDM0bC0xMC4xMzEgNDIuMzY4Yy0uMzI5IDEuMzE2LS43MjQgMi4xNzEtMS4yNSAyLjYzMmMtLjUyNy40Ni0xLjQ0OC42NTgtMi42MzIuNjU4em04NC41NCAxLjc3NmMtMy40MjEgMC02Ljg0Mi0uMzk1LTEwLjEzMi0xLjE4NGMtMy4yODktLjc5LTUuODU1LTEuNjQ1LTcuNTY2LTIuNjMyYy0xLjA1Mi0uNTkyLTEuNzc2LTEuMjUtMi4wMzktMS44NDJhNC42NSA0LjY1IDAgMCAxLS4zOTUtMS44NDJ2LTMuMzU1YzAtMS4zODIuNTI2LTIuMDQgMS41MTMtMi4wNHEuNTkzIDAgMS4xODQuMTk4Yy4zOTUuMTMxLjk4Ny4zOTQgMS42NDUuNjU4YTM1LjggMzUuOCAwIDAgMCA3LjIzNyAyLjMwMmEzOS41IDM5LjUgMCAwIDAgNy44MjkuNzljNC4xNDUgMCA3LjM2OC0uNzI0IDkuNjA1LTIuMTcxYzIuMjM3LTEuNDQ4IDMuNDIxLTMuNTUzIDMuNDIxLTYuMjVjMC0xLjg0Mi0uNTkyLTMuMzU2LTEuNzc2LTQuNjA2cy0zLjQyMS0yLjM2OC02LjY0NS0zLjQyMWwtOS41MzktMi45NmMtNC44MDMtMS41MTMtOC4zNTYtMy43NS0xMC41MjctNi43MWMtMi4xNzEtMi44OTUtMy4yODktNi4xMi0zLjI4OS05LjU0cTAtNC4xNDQgMS43NzYtNy4zMDNjMS4xODQtMi4xMDUgMi43NjMtMy45NDcgNC43MzctNS4zOTRjMS45NzQtMS41MTQgNC4yMTEtMi42MzIgNi44NDItMy40MjJjMi42MzItLjc5IDUuMzk1LTEuMTE4IDguMjktMS4xMThjMS40NDcgMCAyLjk2LjA2NiA0LjQwOC4yNjNjMS41MTMuMTk3IDIuODk0LjQ2IDQuMjc2LjcyNGMxLjMxNi4zMjkgMi41NjYuNjU4IDMuNzUgMS4wNTNxMS43NzYuNTkxIDIuNzYzIDEuMTg0Yy45MjEuNTI2IDEuNTc5IDEuMDUyIDEuOTc0IDEuNjQ0cS41OTIuNzkuNTkyIDIuMTcydjMuMDkyYzAgMS4zODEtLjUyNiAyLjEwNS0xLjUxMyAyLjEwNWMtLjUyNyAwLTEuMzgyLS4yNjMtMi41LS43OXEtNS42MjUtMi41NjUtMTIuNjMyLTIuNTY1Yy0zLjc1IDAtNi43MS41OTItOC43NSAxLjg0MnMtMy4wOTIgMy4xNTgtMy4wOTIgNS44NTVjMCAxLjg0Mi42NTggMy40MjEgMS45NzQgNC42NzFzMy43NSAyLjUgNy4yMzcgMy42MThsOS4zNDIgMi45NmM0LjczNiAxLjUxNCA4LjE1OCAzLjYxOSAxMC4xOTcgNi4zMTdzMy4wMjYgNS43ODkgMy4wMjYgOS4yMWMwIDIuODI5LS41OTIgNS4zOTUtMS43MSA3LjYzMmMtMS4xODQgMi4yMzctMi43NjMgNC4yMS00LjgwMyA1Ljc4OWMtMi4wMzkgMS42NDUtNC40NzQgMi44MjktNy4zMDIgMy42ODVjLTIuOTYxLjkyMS02LjA1MyAxLjM4MS05LjQwOCAxLjM4MSIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIwNy44MzcgMTYyLjgxNmMtMjEuNjQ1IDE1Ljk4Ny01My4wOTIgMjQuNDc0LTgwLjEzMiAyNC40NzRjLTM3Ljg5NCAwLTcyLjA0LTE0LjAxNC05Ny44MjktMzcuMzAzYy0yLjA0LTEuODQyLS4xOTctNC4zNDIgMi4yMzctMi44OTVjMjcuODk1IDE2LjE4NCA2Mi4zMDMgMjUuOTg3IDk3Ljg5NSAyNS45ODdjMjQuMDEzIDAgNTAuMzk1LTUgNzQuNjcxLTE1LjI2M2MzLjYxOC0xLjY0NSA2LjcxIDIuMzY4IDMuMTU4IDUiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIxNi44NSAxNTIuNTUzYy0yLjc2My0zLjU1My0xOC4yODktMS43MTEtMjUuMzI5LS44NTZjLTIuMTA1LjI2NC0yLjQzNC0xLjU3OS0uNTI2LTIuOTZjMTIuMzY4LTguNjg0IDMyLjY5Ny02LjE4NCAzNS4wNjYtMy4yOWMyLjM2OCAyLjk2MS0uNjU4IDIzLjI5LTEyLjIzNyAzMy4wMjdjLTEuNzc3IDEuNTEzLTMuNDg3LjcyMy0yLjY5OC0xLjI1YzIuNjMyLTYuNTEzIDguNDg3LTIxLjE4NSA1LjcyNC0yNC42NzEiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=", - resources: 154, - added: "Jan 25, 2024", - }, - { - id: 14, - account: "aws", - provider_id: 740350143857, - connected: true, - group: "Marketing", - scan_status: "active", - scan_status_time: "3 minutes, 55 seconds", - last_scan: "Jul 17, 2024 3:46am UTC", - next_scan: "Jul 18, 2024 3:46am UTC", - provider_img: - "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgdmlld0JveD0iMCAwIDI1NiAyNTYiPjxnIGZpbGw9Im5vbmUiPjxyZWN0IHdpZHRoPSIyNTYiIGhlaWdodD0iMjU2IiBmaWxsPSIjZjRmMmVkIiByeD0iNjAiLz48cGF0aCBmaWxsPSIjMjUyZjNlIiBkPSJNODQuNzQ1IDExMS45NjFjMCAyLjQzNC4yNjMgNC40MDcuNzIzIDUuODU1YTM1IDM1IDAgMCAwIDIuMTA2IDQuNzM3Yy4zMjkuNTI2LjQ2IDEuMDUyLjQ2IDEuNTEzYzAgLjY1OC0uMzk1IDEuMzE2LTEuMjUgMS45NzNsLTQuMTQ1IDIuNzY0Yy0uNTkyLjM5NC0xLjE4NC41OTItMS43MS41OTJjLS42NTggMC0xLjMxNi0uMzI5LTEuOTc0LS45MjFhMjAuNCAyMC40IDAgMCAxLTIuMzY4LTMuMDkyYTUxIDUxIDAgMCAxLTIuMDQtMy44ODJxLTcuNjk3IDkuMDgtMTkuMzQyIDkuMDc5Yy01LjUyNiAwLTkuOTM0LTEuNTc5LTEzLjE1OC00LjczN2MtMy4yMjMtMy4xNTgtNC44NjgtNy4zNjgtNC44NjgtMTIuNjMxYzAtNS41OTMgMS45NzQtMTAuMTMyIDUuOTg3LTEzLjU1M3M5LjM0Mi01LjEzMiAxNi4xMTgtNS4xMzJjMi4yMzcgMCA0LjU0LjE5OCA2Ljk3NC41MjdzNC45MzQuODU1IDcuNTY2IDEuNDQ3di00LjgwM2MwLTUtMS4wNTMtOC40ODctMy4wOTItMTAuNTI2Yy0yLjEwNi0yLjA0LTUuNjU4LTMuMDI2LTEwLjcyNC0zLjAyNmMtMi4zMDMgMC00LjY3MS4yNjMtNy4xMDUuODU1cy00LjgwMyAxLjMxNi03LjEwNiAyLjIzN2ExOSAxOSAwIDAgMS0yLjMwMi44NTVjLS40Ni4xMzItLjc5LjE5OC0xLjA1My4xOThjLS45MiAwLTEuMzgyLS42NTgtMS4zODItMi4wNHYtMy4yMjRjMC0xLjA1Mi4xMzItMS44NDIuNDYxLTIuMzAycy45MjEtLjkyMSAxLjg0Mi0xLjM4MnEzLjQ1NC0xLjc3NiA4LjI5LTIuOTZjMy4yMjMtLjg1NiA2LjY0NC0xLjI1IDEwLjI2My0xLjI1YzcuODI5IDAgMTMuNTUyIDEuNzc2IDE3LjIzNyA1LjMyOGMzLjYxOCAzLjU1MyA1LjQ2IDguOTQ4IDUuNDYgMTYuMTg1djIxLjMxNnptLTI2LjcxIDEwYzIuMTcgMCA0LjQwNy0uMzk1IDYuNzc2LTEuMTg1YzIuMzY4LS43ODkgNC40NzMtMi4yMzcgNi4yNS00LjIxYzEuMDUyLTEuMjUgMS44NDItMi42MzIgMi4yMzYtNC4yMTFzLjY1OC0zLjQ4Ny42NTgtNS43MjN2LTIuNzY0YTU1IDU1IDAgMCAwLTYuMDUyLTEuMTE4YTUwIDUwIDAgMCAwLTYuMTg1LS4zOTVjLTQuNDA4IDAtNy42MzEuODU2LTkuODAyIDIuNjMycy0zLjIyNCA0LjI3Ni0zLjIyNCA3LjU2NmMwIDMuMDkyLjc5IDUuMzk0IDIuNDM0IDYuOTczYzEuNTggMS42NDUgMy44ODIgMi40MzUgNi45MDggMi40MzVtNTIuODI4IDcuMTA1Yy0xLjE4NCAwLTEuOTc0LS4xOTgtMi41LS42NThjLS41MjYtLjM5NS0uOTg3LTEuMzE2LTEuMzgxLTIuNTY2bC0xNS40Ni01MC44NTVjLS4zOTYtMS4zMTYtLjU5My0yLjE3MS0uNTkzLTIuNjMyYzAtMS4wNTIuNTI2LTEuNjQ1IDEuNTc5LTEuNjQ1aDYuNDQ3YzEuMjUgMCAyLjEwNi4xOTggMi41NjYuNjU4Yy41MjYuMzk1LjkyMSAxLjMxNiAxLjMxNiAyLjU2NmwxMS4wNTIgNDMuNTUzbDEwLjI2NC00My41NTNjLjMyOS0xLjMxNi43MjMtMi4xNyAxLjI1LTIuNTY2Yy41MjYtLjM5NCAxLjQ0Ny0uNjU3IDIuNjMxLS42NTdoNS4yNjNjMS4yNSAwIDIuMTA2LjE5NyAyLjYzMi42NTdjLjUyNi4zOTUuOTg3IDEuMzE2IDEuMjUgMi41NjZsMTAuMzk1IDQ0LjA3OWwxMS4zODEtNDQuMDc5Yy4zOTUtMS4zMTYuODU2LTIuMTcgMS4zMTYtMi41NjZjLjUyNi0uMzk0IDEuMzgyLS42NTcgMi41NjYtLjY1N2g2LjExOGMxLjA1MyAwIDEuNjQ1LjUyNiAxLjY0NSAxLjY0NGMwIC4zMy0uMDY2LjY1OC0uMTMyIDEuMDUzYy0uMDY1LjM5NS0uMTk3LjkyLS40NiAxLjY0NWwtMTUuODU1IDUwLjg1NXEtLjU5MyAxLjk3NC0xLjM4MiAyLjU2NmMtLjUyNi4zOTQtMS4zODIuNjU4LTIuNS42NThoLTUuNjU4Yy0xLjI1IDAtMi4xMDUtLjE5OC0yLjYzMS0uNjU4Yy0uNTI3LS40NjEtLjk4Ny0xLjMxNi0xLjI1LTIuNjMybC0xMC4xOTgtNDIuNDM0bC0xMC4xMzEgNDIuMzY4Yy0uMzI5IDEuMzE2LS43MjQgMi4xNzEtMS4yNSAyLjYzMmMtLjUyNy40Ni0xLjQ0OC42NTgtMi42MzIuNjU4em04NC41NCAxLjc3NmMtMy40MjEgMC02Ljg0Mi0uMzk1LTEwLjEzMi0xLjE4NGMtMy4yODktLjc5LTUuODU1LTEuNjQ1LTcuNTY2LTIuNjMyYy0xLjA1Mi0uNTkyLTEuNzc2LTEuMjUtMi4wMzktMS44NDJhNC42NSA0LjY1IDAgMCAxLS4zOTUtMS44NDJ2LTMuMzU1YzAtMS4zODIuNTI2LTIuMDQgMS41MTMtMi4wNHEuNTkzIDAgMS4xODQuMTk4Yy4zOTUuMTMxLjk4Ny4zOTQgMS42NDUuNjU4YTM1LjggMzUuOCAwIDAgMCA3LjIzNyAyLjMwMmEzOS41IDM5LjUgMCAwIDAgNy44MjkuNzljNC4xNDUgMCA3LjM2OC0uNzI0IDkuNjA1LTIuMTcxYzIuMjM3LTEuNDQ4IDMuNDIxLTMuNTUzIDMuNDIxLTYuMjVjMC0xLjg0Mi0uNTkyLTMuMzU2LTEuNzc2LTQuNjA2cy0zLjQyMS0yLjM2OC02LjY0NS0zLjQyMWwtOS41MzktMi45NmMtNC44MDMtMS41MTMtOC4zNTYtMy43NS0xMC41MjctNi43MWMtMi4xNzEtMi44OTUtMy4yODktNi4xMi0zLjI4OS05LjU0cTAtNC4xNDQgMS43NzYtNy4zMDNjMS4xODQtMi4xMDUgMi43NjMtMy45NDcgNC43MzctNS4zOTRjMS45NzQtMS41MTQgNC4yMTEtMi42MzIgNi44NDItMy40MjJjMi42MzItLjc5IDUuMzk1LTEuMTE4IDguMjktMS4xMThjMS40NDcgMCAyLjk2LjA2NiA0LjQwOC4yNjNjMS41MTMuMTk3IDIuODk0LjQ2IDQuMjc2LjcyNGMxLjMxNi4zMjkgMi41NjYuNjU4IDMuNzUgMS4wNTNxMS43NzYuNTkxIDIuNzYzIDEuMTg0Yy45MjEuNTI2IDEuNTc5IDEuMDUyIDEuOTc0IDEuNjQ0cS41OTIuNzkuNTkyIDIuMTcydjMuMDkyYzAgMS4zODEtLjUyNiAyLjEwNS0xLjUxMyAyLjEwNWMtLjUyNyAwLTEuMzgyLS4yNjMtMi41LS43OXEtNS42MjUtMi41NjUtMTIuNjMyLTIuNTY1Yy0zLjc1IDAtNi43MS41OTItOC43NSAxLjg0MnMtMy4wOTIgMy4xNTgtMy4wOTIgNS44NTVjMCAxLjg0Mi42NTggMy40MjEgMS45NzQgNC42NzFzMy43NSAyLjUgNy4yMzcgMy42MThsOS4zNDIgMi45NmM0LjczNiAxLjUxNCA4LjE1OCAzLjYxOSAxMC4xOTcgNi4zMTdzMy4wMjYgNS43ODkgMy4wMjYgOS4yMWMwIDIuODI5LS41OTIgNS4zOTUtMS43MSA3LjYzMmMtMS4xODQgMi4yMzctMi43NjMgNC4yMS00LjgwMyA1Ljc4OWMtMi4wMzkgMS42NDUtNC40NzQgMi44MjktNy4zMDIgMy42ODVjLTIuOTYxLjkyMS02LjA1MyAxLjM4MS05LjQwOCAxLjM4MSIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIwNy44MzcgMTYyLjgxNmMtMjEuNjQ1IDE1Ljk4Ny01My4wOTIgMjQuNDc0LTgwLjEzMiAyNC40NzRjLTM3Ljg5NCAwLTcyLjA0LTE0LjAxNC05Ny44MjktMzcuMzAzYy0yLjA0LTEuODQyLS4xOTctNC4zNDIgMi4yMzctMi44OTVjMjcuODk1IDE2LjE4NCA2Mi4zMDMgMjUuOTg3IDk3Ljg5NSAyNS45ODdjMjQuMDEzIDAgNTAuMzk1LTUgNzQuNjcxLTE1LjI2M2MzLjYxOC0xLjY0NSA2LjcxIDIuMzY4IDMuMTU4IDUiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIxNi44NSAxNTIuNTUzYy0yLjc2My0zLjU1My0xOC4yODktMS43MTEtMjUuMzI5LS44NTZjLTIuMTA1LjI2NC0yLjQzNC0xLjU3OS0uNTI2LTIuOTZjMTIuMzY4LTguNjg0IDMyLjY5Ny02LjE4NCAzNS4wNjYtMy4yOWMyLjM2OCAyLjk2MS0uNjU4IDIzLjI5LTEyLjIzNyAzMy4wMjdjLTEuNzc3IDEuNTEzLTMuNDg3LjcyMy0yLjY5OC0xLjI1YzIuNjMyLTYuNTEzIDguNDg3LTIxLjE4NSA1LjcyNC0yNC42NzEiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=", - resources: 138, - added: "Mar 12, 2024", - }, - { - id: 15, - account: "aws", - provider_id: 740350143858, - connected: false, - group: "Information Technology", - scan_status: "paused", - scan_status_time: "13 minutes, 25 seconds", - last_scan: "Jul 17, 2024 2:46am UTC", - next_scan: "Jul 18, 2024 2:46am UTC", - provider_img: - "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgdmlld0JveD0iMCAwIDI1NiAyNTYiPjxnIGZpbGw9Im5vbmUiPjxyZWN0IHdpZHRoPSIyNTYiIGhlaWdodD0iMjU2IiBmaWxsPSIjZjRmMmVkIiByeD0iNjAiLz48cGF0aCBmaWxsPSIjMjUyZjNlIiBkPSJNODQuNzQ1IDExMS45NjFjMCAyLjQzNC4yNjMgNC40MDcuNzIzIDUuODU1YTM1IDM1IDAgMCAwIDIuMTA2IDQuNzM3Yy4zMjkuNTI2LjQ2IDEuMDUyLjQ2IDEuNTEzYzAgLjY1OC0uMzk1IDEuMzE2LTEuMjUgMS45NzNsLTQuMTQ1IDIuNzY0Yy0uNTkyLjM5NC0xLjE4NC41OTItMS43MS41OTJjLS42NTggMC0xLjMxNi0uMzI5LTEuOTc0LS45MjFhMjAuNCAyMC40IDAgMCAxLTIuMzY4LTMuMDkyYTUxIDUxIDAgMCAxLTIuMDQtMy44ODJxLTcuNjk3IDkuMDgtMTkuMzQyIDkuMDc5Yy01LjUyNiAwLTkuOTM0LTEuNTc5LTEzLjE1OC00LjczN2MtMy4yMjMtMy4xNTgtNC44NjgtNy4zNjgtNC44NjgtMTIuNjMxYzAtNS41OTMgMS45NzQtMTAuMTMyIDUuOTg3LTEzLjU1M3M5LjM0Mi01LjEzMiAxNi4xMTgtNS4xMzJjMi4yMzcgMCA0LjU0LjE5OCA2Ljk3NC41MjdzNC45MzQuODU1IDcuNTY2IDEuNDQ3di00LjgwM2MwLTUtMS4wNTMtOC40ODctMy4wOTItMTAuNTI2Yy0yLjEwNi0yLjA0LTUuNjU4LTMuMDI2LTEwLjcyNC0zLjAyNmMtMi4zMDMgMC00LjY3MS4yNjMtNy4xMDUuODU1cy00LjgwMyAxLjMxNi03LjEwNiAyLjIzN2ExOSAxOSAwIDAgMS0yLjMwMi44NTVjLS40Ni4xMzItLjc5LjE5OC0xLjA1My4xOThjLS45MiAwLTEuMzgyLS42NTgtMS4zODItMi4wNHYtMy4yMjRjMC0xLjA1Mi4xMzItMS44NDIuNDYxLTIuMzAycy45MjEtLjkyMSAxLjg0Mi0xLjM4MnEzLjQ1NC0xLjc3NiA4LjI5LTIuOTZjMy4yMjMtLjg1NiA2LjY0NC0xLjI1IDEwLjI2My0xLjI1YzcuODI5IDAgMTMuNTUyIDEuNzc2IDE3LjIzNyA1LjMyOGMzLjYxOCAzLjU1MyA1LjQ2IDguOTQ4IDUuNDYgMTYuMTg1djIxLjMxNnptLTI2LjcxIDEwYzIuMTcgMCA0LjQwNy0uMzk1IDYuNzc2LTEuMTg1YzIuMzY4LS43ODkgNC40NzMtMi4yMzcgNi4yNS00LjIxYzEuMDUyLTEuMjUgMS44NDItMi42MzIgMi4yMzYtNC4yMTFzLjY1OC0zLjQ4Ny42NTgtNS43MjN2LTIuNzY0YTU1IDU1IDAgMCAwLTYuMDUyLTEuMTE4YTUwIDUwIDAgMCAwLTYuMTg1LS4zOTVjLTQuNDA4IDAtNy42MzEuODU2LTkuODAyIDIuNjMycy0zLjIyNCA0LjI3Ni0zLjIyNCA3LjU2NmMwIDMuMDkyLjc5IDUuMzk0IDIuNDM0IDYuOTczYzEuNTggMS42NDUgMy44ODIgMi40MzUgNi45MDggMi40MzVtNTIuODI4IDcuMTA1Yy0xLjE4NCAwLTEuOTc0LS4xOTgtMi41LS42NThjLS41MjYtLjM5NS0uOTg3LTEuMzE2LTEuMzgxLTIuNTY2bC0xNS40Ni01MC44NTVjLS4zOTYtMS4zMTYtLjU5My0yLjE3MS0uNTkzLTIuNjMyYzAtMS4wNTIuNTI2LTEuNjQ1IDEuNTc5LTEuNjQ1aDYuNDQ3YzEuMjUgMCAyLjEwNi4xOTggMi41NjYuNjU4Yy41MjYuMzk1LjkyMSAxLjMxNiAxLjMxNiAyLjU2NmwxMS4wNTIgNDMuNTUzbDEwLjI2NC00My41NTNjLjMyOS0xLjMxNi43MjMtMi4xNyAxLjI1LTIuNTY2Yy41MjYtLjM5NCAxLjQ0Ny0uNjU3IDIuNjMxLS42NTdoNS4yNjNjMS4yNSAwIDIuMTA2LjE5NyAyLjYzMi42NTdjLjUyNi4zOTUuOTg3IDEuMzE2IDEuMjUgMi41NjZsMTAuMzk1IDQ0LjA3OWwxMS4zODEtNDQuMDc5Yy4zOTUtMS4zMTYuODU2LTIuMTcgMS4zMTYtMi41NjZjLjUyNi0uMzk0IDEuMzgyLS42NTcgMi41NjYtLjY1N2g2LjExOGMxLjA1MyAwIDEuNjQ1LjUyNiAxLjY0NSAxLjY0NGMwIC4zMy0uMDY2LjY1OC0uMTMyIDEuMDUzYy0uMDY1LjM5NS0uMTk3LjkyLS40NiAxLjY0NWwtMTUuODU1IDUwLjg1NXEtLjU5MyAxLjk3NC0xLjM4MiAyLjU2NmMtLjUyNi4zOTQtMS4zODIuNjU4LTIuNS42NThoLTUuNjU4Yy0xLjI1IDAtMi4xMDUtLjE5OC0yLjYzMS0uNjU4Yy0uNTI3LS40NjEtLjk4Ny0xLjMxNi0xLjI1LTIuNjMybC0xMC4xOTgtNDIuNDM0bC0xMC4xMzEgNDIuMzY4Yy0uMzI5IDEuMzE2LS43MjQgMi4xNzEtMS4yNSAyLjYzMmMtLjUyNy40Ni0xLjQ0OC42NTgtMi42MzIuNjU4em04NC41NCAxLjc3NmMtMy40MjEgMC02Ljg0Mi0uMzk1LTEwLjEzMi0xLjE4NGMtMy4yODktLjc5LTUuODU1LTEuNjQ1LTcuNTY2LTIuNjMyYy0xLjA1Mi0uNTkyLTEuNzc2LTEuMjUtMi4wMzktMS44NDJhNC42NSA0LjY1IDAgMCAxLS4zOTUtMS44NDJ2LTMuMzU1YzAtMS4zODIuNTI2LTIuMDQgMS41MTMtMi4wNHEuNTkzIDAgMS4xODQuMTk4Yy4zOTUuMTMxLjk4Ny4zOTQgMS42NDUuNjU4YTM1LjggMzUuOCAwIDAgMCA3LjIzNyAyLjMwMmEzOS41IDM5LjUgMCAwIDAgNy44MjkuNzljNC4xNDUgMCA3LjM2OC0uNzI0IDkuNjA1LTIuMTcxYzIuMjM3LTEuNDQ4IDMuNDIxLTMuNTUzIDMuNDIxLTYuMjVjMC0xLjg0Mi0uNTkyLTMuMzU2LTEuNzc2LTQuNjA2cy0zLjQyMS0yLjM2OC02LjY0NS0zLjQyMWwtOS41MzktMi45NmMtNC44MDMtMS41MTMtOC4zNTYtMy43NS0xMC41MjctNi43MWMtMi4xNzEtMi44OTUtMy4yODktNi4xMi0zLjI4OS05LjU0cTAtNC4xNDQgMS43NzYtNy4zMDNjMS4xODQtMi4xMDUgMi43NjMtMy45NDcgNC43MzctNS4zOTRjMS45NzQtMS41MTQgNC4yMTEtMi42MzIgNi44NDItMy40MjJjMi42MzItLjc5IDUuMzk1LTEuMTE4IDguMjktMS4xMThjMS40NDcgMCAyLjk2LjA2NiA0LjQwOC4yNjNjMS41MTMuMTk3IDIuODk0LjQ2IDQuMjc2LjcyNGMxLjMxNi4zMjkgMi41NjYuNjU4IDMuNzUgMS4wNTNxMS43NzYuNTkxIDIuNzYzIDEuMTg0Yy45MjEuNTI2IDEuNTc5IDEuMDUyIDEuOTc0IDEuNjQ0cS41OTIuNzkuNTkyIDIuMTcydjMuMDkyYzAgMS4zODEtLjUyNiAyLjEwNS0xLjUxMyAyLjEwNWMtLjUyNyAwLTEuMzgyLS4yNjMtMi41LS43OXEtNS42MjUtMi41NjUtMTIuNjMyLTIuNTY1Yy0zLjc1IDAtNi43MS41OTItOC43NSAxLjg0MnMtMy4wOTIgMy4xNTgtMy4wOTIgNS44NTVjMCAxLjg0Mi42NTggMy40MjEgMS45NzQgNC42NzFzMy43NSAyLjUgNy4yMzcgMy42MThsOS4zNDIgMi45NmM0LjczNiAxLjUxNCA4LjE1OCAzLjYxOSAxMC4xOTcgNi4zMTdzMy4wMjYgNS43ODkgMy4wMjYgOS4yMWMwIDIuODI5LS41OTIgNS4zOTUtMS43MSA3LjYzMmMtMS4xODQgMi4yMzctMi43NjMgNC4yMS00LjgwMyA1Ljc4OWMtMi4wMzkgMS42NDUtNC40NzQgMi44MjktNy4zMDIgMy42ODVjLTIuOTYxLjkyMS02LjA1MyAxLjM4MS05LjQwOCAxLjM4MSIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIwNy44MzcgMTYyLjgxNmMtMjEuNjQ1IDE1Ljk4Ny01My4wOTIgMjQuNDc0LTgwLjEzMiAyNC40NzRjLTM3Ljg5NCAwLTcyLjA0LTE0LjAxNC05Ny44MjktMzcuMzAzYy0yLjA0LTEuODQyLS4xOTctNC4zNDIgMi4yMzctMi44OTVjMjcuODk1IDE2LjE4NCA2Mi4zMDMgMjUuOTg3IDk3Ljg5NSAyNS45ODdjMjQuMDEzIDAgNTAuMzk1LTUgNzQuNjcxLTE1LjI2M2MzLjYxOC0xLjY0NSA2LjcxIDIuMzY4IDMuMTU4IDUiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIxNi44NSAxNTIuNTUzYy0yLjc2My0zLjU1My0xOC4yODktMS43MTEtMjUuMzI5LS44NTZjLTIuMTA1LjI2NC0yLjQzNC0xLjU3OS0uNTI2LTIuOTZjMTIuMzY4LTguNjg0IDMyLjY5Ny02LjE4NCAzNS4wNjYtMy4yOWMyLjM2OCAyLjk2MS0uNjU4IDIzLjI5LTEyLjIzNyAzMy4wMjdjLTEuNzc3IDEuNTEzLTMuNDg3LjcyMy0yLjY5OC0xLjI1YzIuNjMyLTYuNTEzIDguNDg3LTIxLjE4NSA1LjcyNC0yNC42NzEiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=", - resources: 112, - added: "Feb 17, 2024", - }, - { - id: 16, - account: "aws", - provider_id: 740350143859, - connected: true, - group: "Sales", - scan_status: "active", - scan_status_time: "6 minutes, 10 seconds", - last_scan: "Jul 17, 2024 1:46am UTC", - next_scan: "Jul 18, 2024 1:46am UTC", - provider_img: - "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgdmlld0JveD0iMCAwIDI1NiAyNTYiPjxnIGZpbGw9Im5vbmUiPjxyZWN0IHdpZHRoPSIyNTYiIGhlaWdodD0iMjU2IiBmaWxsPSIjZjRmMmVkIiByeD0iNjAiLz48cGF0aCBmaWxsPSIjMjUyZjNlIiBkPSJNODQuNzQ1IDExMS45NjFjMCAyLjQzNC4yNjMgNC40MDcuNzIzIDUuODU1YTM1IDM1IDAgMCAwIDIuMTA2IDQuNzM3Yy4zMjkuNTI2LjQ2IDEuMDUyLjQ2IDEuNTEzYzAgLjY1OC0uMzk1IDEuMzE2LTEuMjUgMS45NzNsLTQuMTQ1IDIuNzY0Yy0uNTkyLjM5NC0xLjE4NC41OTItMS43MS41OTJjLS42NTggMC0xLjMxNi0uMzI5LTEuOTc0LS45MjFhMjAuNCAyMC40IDAgMCAxLTIuMzY4LTMuMDkyYTUxIDUxIDAgMCAxLTIuMDQtMy44ODJxLTcuNjk3IDkuMDgtMTkuMzQyIDkuMDc5Yy01LjUyNiAwLTkuOTM0LTEuNTc5LTEzLjE1OC00LjczN2MtMy4yMjMtMy4xNTgtNC44NjgtNy4zNjgtNC44NjgtMTIuNjMxYzAtNS41OTMgMS45NzQtMTAuMTMyIDUuOTg3LTEzLjU1M3M5LjM0Mi01LjEzMiAxNi4xMTgtNS4xMzJjMi4yMzcgMCA0LjU0LjE5OCA2Ljk3NC41MjdzNC45MzQuODU1IDcuNTY2IDEuNDQ3di00LjgwM2MwLTUtMS4wNTMtOC40ODctMy4wOTItMTAuNTI2Yy0yLjEwNi0yLjA0LTUuNjU4LTMuMDI2LTEwLjcyNC0zLjAyNmMtMi4zMDMgMC00LjY3MS4yNjMtNy4xMDUuODU1cy00LjgwMyAxLjMxNi03LjEwNiAyLjIzN2ExOSAxOSAwIDAgMS0yLjMwMi44NTVjLS40Ni4xMzItLjc5LjE5OC0xLjA1My4xOThjLS45MiAwLTEuMzgyLS42NTgtMS4zODItMi4wNHYtMy4yMjRjMC0xLjA1Mi4xMzItMS44NDIuNDYxLTIuMzAycy45MjEtLjkyMSAxLjg0Mi0xLjM4MnEzLjQ1NC0xLjc3NiA4LjI5LTIuOTZjMy4yMjMtLjg1NiA2LjY0NC0xLjI1IDEwLjI2My0xLjI1YzcuODI5IDAgMTMuNTUyIDEuNzc2IDE3LjIzNyA1LjMyOGMzLjYxOCAzLjU1MyA1LjQ2IDguOTQ4IDUuNDYgMTYuMTg1djIxLjMxNnptLTI2LjcxIDEwYzIuMTcgMCA0LjQwNy0uMzk1IDYuNzc2LTEuMTg1YzIuMzY4LS43ODkgNC40NzMtMi4yMzcgNi4yNS00LjIxYzEuMDUyLTEuMjUgMS44NDItMi42MzIgMi4yMzYtNC4yMTFzLjY1OC0zLjQ4Ny42NTgtNS43MjN2LTIuNzY0YTU1IDU1IDAgMCAwLTYuMDUyLTEuMTE4YTUwIDUwIDAgMCAwLTYuMTg1LS4zOTVjLTQuNDA4IDAtNy42MzEuODU2LTkuODAyIDIuNjMycy0zLjIyNCA0LjI3Ni0zLjIyNCA3LjU2NmMwIDMuMDkyLjc5IDUuMzk0IDIuNDM0IDYuOTczYzEuNTggMS42NDUgMy44ODIgMi40MzUgNi45MDggMi40MzVtNTIuODI4IDcuMTA1Yy0xLjE4NCAwLTEuOTc0LS4xOTgtMi41LS42NThjLS41MjYtLjM5NS0uOTg3LTEuMzE2LTEuMzgxLTIuNTY2bC0xNS40Ni01MC44NTVjLS4zOTYtMS4zMTYtLjU5My0yLjE3MS0uNTkzLTIuNjMyYzAtMS4wNTIuNTI2LTEuNjQ1IDEuNTc5LTEuNjQ1aDYuNDQ3YzEuMjUgMCAyLjEwNi4xOTggMi41NjYuNjU4Yy41MjYuMzk1LjkyMSAxLjMxNiAxLjMxNiAyLjU2NmwxMS4wNTIgNDMuNTUzbDEwLjI2NC00My41NTNjLjMyOS0xLjMxNi43MjMtMi4xNyAxLjI1LTIuNTY2Yy41MjYtLjM5NCAxLjQ0Ny0uNjU3IDIuNjMxLS42NTdoNS4yNjNjMS4yNSAwIDIuMTA2LjE5NyAyLjYzMi42NTdjLjUyNi4zOTUuOTg3IDEuMzE2IDEuMjUgMi41NjZsMTAuMzk1IDQ0LjA3OWwxMS4zODEtNDQuMDc5Yy4zOTUtMS4zMTYuODU2LTIuMTcgMS4zMTYtMi41NjZjLjUyNi0uMzk0IDEuMzgyLS42NTcgMi41NjYtLjY1N2g2LjExOGMxLjA1MyAwIDEuNjQ1LjUyNiAxLjY0NSAxLjY0NGMwIC4zMy0uMDY2LjY1OC0uMTMyIDEuMDUzYy0uMDY1LjM5NS0uMTk3LjkyLS40NiAxLjY0NWwtMTUuODU1IDUwLjg1NXEtLjU5MyAxLjk3NC0xLjM4MiAyLjU2NmMtLjUyNi4zOTQtMS4zODIuNjU4LTIuNS42NThoLTUuNjU4Yy0xLjI1IDAtMi4xMDUtLjE5OC0yLjYzMS0uNjU4Yy0uNTI3LS40NjEtLjk4Ny0xLjMxNi0xLjI1LTIuNjMybC0xMC4xOTgtNDIuNDM0bC0xMC4xMzEgNDIuMzY4Yy0uMzI5IDEuMzE2LS43MjQgMi4xNzEtMS4yNSAyLjYzMmMtLjUyNy40Ni0xLjQ0OC42NTgtMi42MzIuNjU4em04NC41NCAxLjc3NmMtMy40MjEgMC02Ljg0Mi0uMzk1LTEwLjEzMi0xLjE4NGMtMy4yODktLjc5LTUuODU1LTEuNjQ1LTcuNTY2LTIuNjMyYy0xLjA1Mi0uNTkyLTEuNzc2LTEuMjUtMi4wMzktMS44NDJhNC42NSA0LjY1IDAgMCAxLS4zOTUtMS44NDJ2LTMuMzU1YzAtMS4zODIuNTI2LTIuMDQgMS41MTMtMi4wNHEuNTkzIDAgMS4xODQuMTk4Yy4zOTUuMTMxLjk4Ny4zOTQgMS42NDUuNjU4YTM1LjggMzUuOCAwIDAgMCA3LjIzNyAyLjMwMmEzOS41IDM5LjUgMCAwIDAgNy44MjkuNzljNC4xNDUgMCA3LjM2OC0uNzI0IDkuNjA1LTIuMTcxYzIuMjM3LTEuNDQ4IDMuNDIxLTMuNTUzIDMuNDIxLTYuMjVjMC0xLjg0Mi0uNTkyLTMuMzU2LTEuNzc2LTQuNjA2cy0zLjQyMS0yLjM2OC02LjY0NS0zLjQyMWwtOS41MzktMi45NmMtNC44MDMtMS41MTMtOC4zNTYtMy43NS0xMC41MjctNi43MWMtMi4xNzEtMi44OTUtMy4yODktNi4xMi0zLjI4OS05LjU0cTAtNC4xNDQgMS43NzYtNy4zMDNjMS4xODQtMi4xMDUgMi43NjMtMy45NDcgNC43MzctNS4zOTRjMS45NzQtMS41MTQgNC4yMTEtMi42MzIgNi44NDItMy40MjJjMi42MzItLjc5IDUuMzk1LTEuMTE4IDguMjktMS4xMThjMS40NDcgMCAyLjk2LjA2NiA0LjQwOC4yNjNjMS41MTMuMTk3IDIuODk0LjQ2IDQuMjc2LjcyNGMxLjMxNi4zMjkgMi41NjYuNjU4IDMuNzUgMS4wNTNxMS43NzYuNTkxIDIuNzYzIDEuMTg0Yy45MjEuNTI2IDEuNTc5IDEuMDUyIDEuOTc0IDEuNjQ0cS41OTIuNzkuNTkyIDIuMTcydjMuMDkyYzAgMS4zODEtLjUyNiAyLjEwNS0xLjUxMyAyLjEwNWMtLjUyNyAwLTEuMzgyLS4yNjMtMi41LS43OXEtNS42MjUtMi41NjUtMTIuNjMyLTIuNTY1Yy0zLjc1IDAtNi43MS41OTItOC43NSAxLjg0MnMtMy4wOTIgMy4xNTgtMy4wOTIgNS44NTVjMCAxLjg0Mi42NTggMy40MjEgMS45NzQgNC42NzFzMy43NSAyLjUgNy4yMzcgMy42MThsOS4zNDIgMi45NmM0LjczNiAxLjUxNCA4LjE1OCAzLjYxOSAxMC4xOTcgNi4zMTdzMy4wMjYgNS43ODkgMy4wMjYgOS4yMWMwIDIuODI5LS41OTIgNS4zOTUtMS43MSA3LjYzMmMtMS4xODQgMi4yMzctMi43NjMgNC4yMS00LjgwMyA1Ljc4OWMtMi4wMzkgMS42NDUtNC40NzQgMi44MjktNy4zMDIgMy42ODVjLTIuOTYxLjkyMS02LjA1MyAxLjM4MS05LjQwOCAxLjM4MSIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIwNy44MzcgMTYyLjgxNmMtMjEuNjQ1IDE1Ljk4Ny01My4wOTIgMjQuNDc0LTgwLjEzMiAyNC40NzRjLTM3Ljg5NCAwLTcyLjA0LTE0LjAxNC05Ny44MjktMzcuMzAzYy0yLjA0LTEuODQyLS4xOTctNC4zNDIgMi4yMzctMi44OTVjMjcuODk1IDE2LjE4NCA2Mi4zMDMgMjUuOTg3IDk3Ljg5NSAyNS45ODdjMjQuMDEzIDAgNTAuMzk1LTUgNzQuNjcxLTE1LjI2M2MzLjYxOC0xLjY0NSA2LjcxIDIuMzY4IDMuMTU4IDUiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIxNi44NSAxNTIuNTUzYy0yLjc2My0zLjU1My0xOC4yODktMS43MTEtMjUuMzI5LS44NTZjLTIuMTA1LjI2NC0yLjQzNC0xLjU3OS0uNTI2LTIuOTZjMTIuMzY4LTguNjg0IDMyLjY5Ny02LjE4NCAzNS4wNjYtMy4yOWMyLjM2OCAyLjk2MS0uNjU4IDIzLjI5LTEyLjIzNyAzMy4wMjdjLTEuNzc3IDEuNTEzLTMuNDg3LjcyMy0yLjY5OC0xLjI1YzIuNjMyLTYuNTEzIDguNDg3LTIxLjE4NSA1LjcyNC0yNC42NzEiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=", - resources: 140, - added: "Apr 1, 2024", - }, - { - id: 17, - account: "aws", - provider_id: 740350143860, - connected: true, - group: "Analysis", - scan_status: "active", - scan_status_time: "5 minutes, 30 seconds", - last_scan: "Jul 17, 2024 12:46am UTC", - next_scan: "Jul 18, 2024 12:46am UTC", - provider_img: - "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgdmlld0JveD0iMCAwIDI1NiAyNTYiPjxnIGZpbGw9Im5vbmUiPjxyZWN0IHdpZHRoPSIyNTYiIGhlaWdodD0iMjU2IiBmaWxsPSIjZjRmMmVkIiByeD0iNjAiLz48cGF0aCBmaWxsPSIjMjUyZjNlIiBkPSJNODQuNzQ1IDExMS45NjFjMCAyLjQzNC4yNjMgNC40MDcuNzIzIDUuODU1YTM1IDM1IDAgMCAwIDIuMTA2IDQuNzM3Yy4zMjkuNTI2LjQ2IDEuMDUyLjQ2IDEuNTEzYzAgLjY1OC0uMzk1IDEuMzE2LTEuMjUgMS45NzNsLTQuMTQ1IDIuNzY0Yy0uNTkyLjM5NC0xLjE4NC41OTItMS43MS41OTJjLS42NTggMC0xLjMxNi0uMzI5LTEuOTc0LS45MjFhMjAuNCAyMC40IDAgMCAxLTIuMzY4LTMuMDkyYTUxIDUxIDAgMCAxLTIuMDQtMy44ODJxLTcuNjk3IDkuMDgtMTkuMzQyIDkuMDc5Yy01LjUyNiAwLTkuOTM0LTEuNTc5LTEzLjE1OC00LjczN2MtMy4yMjMtMy4xNTgtNC44NjgtNy4zNjgtNC44NjgtMTIuNjMxYzAtNS41OTMgMS45NzQtMTAuMTMyIDUuOTg3LTEzLjU1M3M5LjM0Mi01LjEzMiAxNi4xMTgtNS4xMzJjMi4yMzcgMCA0LjU0LjE5OCA2Ljk3NC41MjdzNC45MzQuODU1IDcuNTY2IDEuNDQ3di00LjgwM2MwLTUtMS4wNTMtOC40ODctMy4wOTItMTAuNTI2Yy0yLjEwNi0yLjA0LTUuNjU4LTMuMDI2LTEwLjcyNC0zLjAyNmMtMi4zMDMgMC00LjY3MS4yNjMtNy4xMDUuODU1cy00LjgwMyAxLjMxNi03LjEwNiAyLjIzN2ExOSAxOSAwIDAgMS0yLjMwMi44NTVjLS40Ni4xMzItLjc5LjE5OC0xLjA1My4xOThjLS45MiAwLTEuMzgyLS42NTgtMS4zODItMi4wNHYtMy4yMjRjMC0xLjA1Mi4xMzItMS44NDIuNDYxLTIuMzAycy45MjEtLjkyMSAxLjg0Mi0xLjM4MnEzLjQ1NC0xLjc3NiA4LjI5LTIuOTZjMy4yMjMtLjg1NiA2LjY0NC0xLjI1IDEwLjI2My0xLjI1YzcuODI5IDAgMTMuNTUyIDEuNzc2IDE3LjIzNyA1LjMyOGMzLjYxOCAzLjU1MyA1LjQ2IDguOTQ4IDUuNDYgMTYuMTg1djIxLjMxNnptLTI2LjcxIDEwYzIuMTcgMCA0LjQwNy0uMzk1IDYuNzc2LTEuMTg1YzIuMzY4LS43ODkgNC40NzMtMi4yMzcgNi4yNS00LjIxYzEuMDUyLTEuMjUgMS44NDItMi42MzIgMi4yMzYtNC4yMTFzLjY1OC0zLjQ4Ny42NTgtNS43MjN2LTIuNzY0YTU1IDU1IDAgMCAwLTYuMDUyLTEuMTE4YTUwIDUwIDAgMCAwLTYuMTg1LS4zOTVjLTQuNDA4IDAtNy42MzEuODU2LTkuODAyIDIuNjMycy0zLjIyNCA0LjI3Ni0zLjIyNCA3LjU2NmMwIDMuMDkyLjc5IDUuMzk0IDIuNDM0IDYuOTczYzEuNTggMS42NDUgMy44ODIgMi40MzUgNi45MDggMi40MzVtNTIuODI4IDcuMTA1Yy0xLjE4NCAwLTEuOTc0LS4xOTgtMi41LS42NThjLS41MjYtLjM5NS0uOTg3LTEuMzE2LTEuMzgxLTIuNTY2bC0xNS40Ni01MC44NTVjLS4zOTYtMS4zMTYtLjU5My0yLjE3MS0uNTkzLTIuNjMyYzAtMS4wNTIuNTI2LTEuNjQ1IDEuNTc5LTEuNjQ1aDYuNDQ3YzEuMjUgMCAyLjEwNi4xOTggMi41NjYuNjU4Yy41MjYuMzk1LjkyMSAxLjMxNiAxLjMxNiAyLjU2NmwxMS4wNTIgNDMuNTUzbDEwLjI2NC00My41NTNjLjMyOS0xLjMxNi43MjMtMi4xNyAxLjI1LTIuNTY2Yy41MjYtLjM5NCAxLjQ0Ny0uNjU3IDIuNjMxLS42NTdoNS4yNjNjMS4yNSAwIDIuMTA2LjE5NyAyLjYzMi42NTdjLjUyNi4zOTUuOTg3IDEuMzE2IDEuMjUgMi41NjZsMTAuMzk1IDQ0LjA3OWwxMS4zODEtNDQuMDc5Yy4zOTUtMS4zMTYuODU2LTIuMTcgMS4zMTYtMi41NjZjLjUyNi0uMzk0IDEuMzgyLS42NTcgMi41NjYtLjY1N2g2LjExOGMxLjA1MyAwIDEuNjQ1LjUyNiAxLjY0NSAxLjY0NGMwIC4zMy0uMDY2LjY1OC0uMTMyIDEuMDUzYy0uMDY1LjM5NS0uMTk3LjkyLS40NiAxLjY0NWwtMTUuODU1IDUwLjg1NXEtLjU5MyAxLjk3NC0xLjM4MiAyLjU2NmMtLjUyNi4zOTQtMS4zODIuNjU4LTIuNS42NThoLTUuNjU4Yy0xLjI1IDAtMi4xMDUtLjE5OC0yLjYzMS0uNjU4Yy0uNTI3LS40NjEtLjk4Ny0xLjMxNi0xLjI1LTIuNjMybC0xMC4xOTgtNDIuNDM0bC0xMC4xMzEgNDIuMzY4Yy0uMzI5IDEuMzE2LS43MjQgMi4xNzEtMS4yNSAyLjYzMmMtLjUyNy40Ni0xLjQ0OC42NTgtMi42MzIuNjU4em04NC41NCAxLjc3NmMtMy40MjEgMC02Ljg0Mi0uMzk1LTEwLjEzMi0xLjE4NGMtMy4yODktLjc5LTUuODU1LTEuNjQ1LTcuNTY2LTIuNjMyYy0xLjA1Mi0uNTkyLTEuNzc2LTEuMjUtMi4wMzktMS44NDJhNC42NSA0LjY1IDAgMCAxLS4zOTUtMS44NDJ2LTMuMzU1YzAtMS4zODIuNTI2LTIuMDQgMS41MTMtMi4wNHEuNTkzIDAgMS4xODQuMTk4Yy4zOTUuMTMxLjk4Ny4zOTQgMS42NDUuNjU4YTM1LjggMzUuOCAwIDAgMCA3LjIzNyAyLjMwMmEzOS41IDM5LjUgMCAwIDAgNy44MjkuNzljNC4xNDUgMCA3LjM2OC0uNzI0IDkuNjA1LTIuMTcxYzIuMjM3LTEuNDQ4IDMuNDIxLTMuNTUzIDMuNDIxLTYuMjVjMC0xLjg0Mi0uNTkyLTMuMzU2LTEuNzc2LTQuNjA2cy0zLjQyMS0yLjM2OC02LjY0NS0zLjQyMWwtOS41MzktMi45NmMtNC44MDMtMS41MTMtOC4zNTYtMy43NS0xMC41MjctNi43MWMtMi4xNzEtMi44OTUtMy4yODktNi4xMi0zLjI4OS05LjU0cTAtNC4xNDQgMS43NzYtNy4zMDNjMS4xODQtMi4xMDUgMi43NjMtMy45NDcgNC43MzctNS4zOTRjMS45NzQtMS41MTQgNC4yMTEtMi42MzIgNi44NDItMy40MjJjMi42MzItLjc5IDUuMzk1LTEuMTE4IDguMjktMS4xMThjMS40NDcgMCAyLjk2LjA2NiA0LjQwOC4yNjNjMS41MTMuMTk3IDIuODk0LjQ2IDQuMjc2LjcyNGMxLjMxNi4zMjkgMi41NjYuNjU4IDMuNzUgMS4wNTNxMS43NzYuNTkxIDIuNzYzIDEuMTg0Yy45MjEuNTI2IDEuNTc5IDEuMDUyIDEuOTc0IDEuNjQ0cS41OTIuNzkuNTkyIDIuMTcydjMuMDkyYzAgMS4zODEtLjUyNiAyLjEwNS0xLjUxMyAyLjEwNWMtLjUyNyAwLTEuMzgyLS4yNjMtMi41LS43OXEtNS42MjUtMi41NjUtMTIuNjMyLTIuNTY1Yy0zLjc1IDAtNi43MS41OTItOC43NSAxLjg0MnMtMy4wOTIgMy4xNTgtMy4wOTIgNS44NTVjMCAxLjg0Mi42NTggMy40MjEgMS45NzQgNC42NzFzMy43NSAyLjUgNy4yMzcgMy42MThsOS4zNDIgMi45NmM0LjczNiAxLjUxNCA4LjE1OCAzLjYxOSAxMC4xOTcgNi4zMTdzMy4wMjYgNS43ODkgMy4wMjYgOS4yMWMwIDIuODI5LS41OTIgNS4zOTUtMS43MSA3LjYzMmMtMS4xODQgMi4yMzctMi43NjMgNC4yMS00LjgwMyA1Ljc4OWMtMi4wMzkgMS42NDUtNC40NzQgMi44MjktNy4zMDIgMy42ODVjLTIuOTYxLjkyMS02LjA1MyAxLjM4MS05LjQwOCAxLjM4MSIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIwNy44MzcgMTYyLjgxNmMtMjEuNjQ1IDE1Ljk4Ny01My4wOTIgMjQuNDc0LTgwLjEzMiAyNC40NzRjLTM3Ljg5NCAwLTcyLjA0LTE0LjAxNC05Ny44MjktMzcuMzAzYy0yLjA0LTEuODQyLS4xOTctNC4zNDIgMi4yMzctMi44OTVjMjcuODk1IDE2LjE4NCA2Mi4zMDMgMjUuOTg3IDk3Ljg5NSAyNS45ODdjMjQuMDEzIDAgNTAuMzk1LTUgNzQuNjcxLTE1LjI2M2MzLjYxOC0xLjY0NSA2LjcxIDIuMzY4IDMuMTU4IDUiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIxNi44NSAxNTIuNTUzYy0yLjc2My0zLjU1My0xOC4yODktMS43MTEtMjUuMzI5LS44NTZjLTIuMTA1LjI2NC0yLjQzNC0xLjU3OS0uNTI2LTIuOTZjMTIuMzY4LTguNjg0IDMyLjY5Ny02LjE4NCAzNS4wNjYtMy4yOWMyLjM2OCAyLjk2MS0uNjU4IDIzLjI5LTEyLjIzNyAzMy4wMjdjLTEuNzc3IDEuNTEzLTMuNDg3LjcyMy0yLjY5OC0xLjI1YzIuNjMyLTYuNTEzIDguNDg3LTIxLjE4NSA1LjcyNC0yNC42NzEiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=", - resources: 126, - added: "Jan 30, 2024", - }, - { - id: 18, - account: "aws", - provider_id: 740350143861, - connected: true, - group: "Testing", - scan_status: "active", - scan_status_time: "8 minutes, 5 seconds", - last_scan: "Jul 16, 2024 11:46pm UTC", - next_scan: "Jul 17, 2024 11:46pm UTC", - provider_img: - "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgdmlld0JveD0iMCAwIDI1NiAyNTYiPjxnIGZpbGw9Im5vbmUiPjxyZWN0IHdpZHRoPSIyNTYiIGhlaWdodD0iMjU2IiBmaWxsPSIjZjRmMmVkIiByeD0iNjAiLz48cGF0aCBmaWxsPSIjMjUyZjNlIiBkPSJNODQuNzQ1IDExMS45NjFjMCAyLjQzNC4yNjMgNC40MDcuNzIzIDUuODU1YTM1IDM1IDAgMCAwIDIuMTA2IDQuNzM3Yy4zMjkuNTI2LjQ2IDEuMDUyLjQ2IDEuNTEzYzAgLjY1OC0uMzk1IDEuMzE2LTEuMjUgMS45NzNsLTQuMTQ1IDIuNzY0Yy0uNTkyLjM5NC0xLjE4NC41OTItMS43MS41OTJjLS42NTggMC0xLjMxNi0uMzI5LTEuOTc0LS45MjFhMjAuNCAyMC40IDAgMCAxLTIuMzY4LTMuMDkyYTUxIDUxIDAgMCAxLTIuMDQtMy44ODJxLTcuNjk3IDkuMDgtMTkuMzQyIDkuMDc5Yy01LjUyNiAwLTkuOTM0LTEuNTc5LTEzLjE1OC00LjczN2MtMy4yMjMtMy4xNTgtNC44NjgtNy4zNjgtNC44NjgtMTIuNjMxYzAtNS41OTMgMS45NzQtMTAuMTMyIDUuOTg3LTEzLjU1M3M5LjM0Mi01LjEzMiAxNi4xMTgtNS4xMzJjMi4yMzcgMCA0LjU0LjE5OCA2Ljk3NC41MjdzNC45MzQuODU1IDcuNTY2IDEuNDQ3di00LjgwM2MwLTUtMS4wNTMtOC40ODctMy4wOTItMTAuNTI2Yy0yLjEwNi0yLjA0LTUuNjU4LTMuMDI2LTEwLjcyNC0zLjAyNmMtMi4zMDMgMC00LjY3MS4yNjMtNy4xMDUuODU1cy00LjgwMyAxLjMxNi03LjEwNiAyLjIzN2ExOSAxOSAwIDAgMS0yLjMwMi44NTVjLS40Ni4xMzItLjc5LjE5OC0xLjA1My4xOThjLS45MiAwLTEuMzgyLS42NTgtMS4zODItMi4wNHYtMy4yMjRjMC0xLjA1Mi4xMzItMS44NDIuNDYxLTIuMzAycy45MjEtLjkyMSAxLjg0Mi0xLjM4MnEzLjQ1NC0xLjc3NiA4LjI5LTIuOTZjMy4yMjMtLjg1NiA2LjY0NC0xLjI1IDEwLjI2My0xLjI1YzcuODI5IDAgMTMuNTUyIDEuNzc2IDE3LjIzNyA1LjMyOGMzLjYxOCAzLjU1MyA1LjQ2IDguOTQ4IDUuNDYgMTYuMTg1djIxLjMxNnptLTI2LjcxIDEwYzIuMTcgMCA0LjQwNy0uMzk1IDYuNzc2LTEuMTg1YzIuMzY4LS43ODkgNC40NzMtMi4yMzcgNi4yNS00LjIxYzEuMDUyLTEuMjUgMS44NDItMi42MzIgMi4yMzYtNC4yMTFzLjY1OC0zLjQ4Ny42NTgtNS43MjN2LTIuNzY0YTU1IDU1IDAgMCAwLTYuMDUyLTEuMTE4YTUwIDUwIDAgMCAwLTYuMTg1LS4zOTVjLTQuNDA4IDAtNy42MzEuODU2LTkuODAyIDIuNjMycy0zLjIyNCA0LjI3Ni0zLjIyNCA3LjU2NmMwIDMuMDkyLjc5IDUuMzk0IDIuNDM0IDYuOTczYzEuNTggMS42NDUgMy44ODIgMi40MzUgNi45MDggMi40MzVtNTIuODI4IDcuMTA1Yy0xLjE4NCAwLTEuOTc0LS4xOTgtMi41LS42NThjLS41MjYtLjM5NS0uOTg3LTEuMzE2LTEuMzgxLTIuNTY2bC0xNS40Ni01MC44NTVjLS4zOTYtMS4zMTYtLjU5My0yLjE3MS0uNTkzLTIuNjMyYzAtMS4wNTIuNTI2LTEuNjQ1IDEuNTc5LTEuNjQ1aDYuNDQ3YzEuMjUgMCAyLjEwNi4xOTggMi41NjYuNjU4Yy41MjYuMzk1LjkyMSAxLjMxNiAxLjMxNiAyLjU2NmwxMS4wNTIgNDMuNTUzbDEwLjI2NC00My41NTNjLjMyOS0xLjMxNi43MjMtMi4xNyAxLjI1LTIuNTY2Yy41MjYtLjM5NCAxLjQ0Ny0uNjU3IDIuNjMxLS42NTdoNS4yNjNjMS4yNSAwIDIuMTA2LjE5NyAyLjYzMi42NTdjLjUyNi4zOTUuOTg3IDEuMzE2IDEuMjUgMi41NjZsMTAuMzk1IDQ0LjA3OWwxMS4zODEtNDQuMDc5Yy4zOTUtMS4zMTYuODU2LTIuMTcgMS4zMTYtMi41NjZjLjUyNi0uMzk0IDEuMzgyLS42NTcgMi41NjYtLjY1N2g2LjExOGMxLjA1MyAwIDEuNjQ1LjUyNiAxLjY0NSAxLjY0NGMwIC4zMy0uMDY2LjY1OC0uMTMyIDEuMDUzYy0uMDY1LjM5NS0uMTk3LjkyLS40NiAxLjY0NWwtMTUuODU1IDUwLjg1NXEtLjU5MyAxLjk3NC0xLjM4MiAyLjU2NmMtLjUyNi4zOTQtMS4zODIuNjU4LTIuNS42NThoLTUuNjU4Yy0xLjI1IDAtMi4xMDUtLjE5OC0yLjYzMS0uNjU4Yy0uNTI3LS40NjEtLjk4Ny0xLjMxNi0xLjI1LTIuNjMybC0xMC4xOTgtNDIuNDM0bC0xMC4xMzEgNDIuMzY4Yy0uMzI5IDEuMzE2LS43MjQgMi4xNzEtMS4yNSAyLjYzMmMtLjUyNy40Ni0xLjQ0OC42NTgtMi42MzIuNjU4em04NC41NCAxLjc3NmMtMy40MjEgMC02Ljg0Mi0uMzk1LTEwLjEzMi0xLjE4NGMtMy4yODktLjc5LTUuODU1LTEuNjQ1LTcuNTY2LTIuNjMyYy0xLjA1Mi0uNTkyLTEuNzc2LTEuMjUtMi4wMzktMS44NDJhNC42NSA0LjY1IDAgMCAxLS4zOTUtMS44NDJ2LTMuMzU1YzAtMS4zODIuNTI2LTIuMDQgMS41MTMtMi4wNHEuNTkzIDAgMS4xODQuMTk4Yy4zOTUuMTMxLjk4Ny4zOTQgMS42NDUuNjU4YTM1LjggMzUuOCAwIDAgMCA3LjIzNyAyLjMwMmEzOS41IDM5LjUgMCAwIDAgNy44MjkuNzljNC4xNDUgMCA3LjM2OC0uNzI0IDkuNjA1LTIuMTcxYzIuMjM3LTEuNDQ4IDMuNDIxLTMuNTUzIDMuNDIxLTYuMjVjMC0xLjg0Mi0uNTkyLTMuMzU2LTEuNzc2LTQuNjA2cy0zLjQyMS0yLjM2OC02LjY0NS0zLjQyMWwtOS41MzktMi45NmMtNC44MDMtMS41MTMtOC4zNTYtMy43NS0xMC41MjctNi43MWMtMi4xNzEtMi44OTUtMy4yODktNi4xMi0zLjI4OS05LjU0cTAtNC4xNDQgMS43NzYtNy4zMDNjMS4xODQtMi4xMDUgMi43NjMtMy45NDcgNC43MzctNS4zOTRjMS45NzQtMS41MTQgNC4yMTEtMi42MzIgNi44NDItMy40MjJjMi42MzItLjc5IDUuMzk1LTEuMTE4IDguMjktMS4xMThjMS40NDcgMCAyLjk2LjA2NiA0LjQwOC4yNjNjMS41MTMuMTk3IDIuODk0LjQ2IDQuMjc2LjcyNGMxLjMxNi4zMjkgMi41NjYuNjU4IDMuNzUgMS4wNTNxMS43NzYuNTkxIDIuNzYzIDEuMTg0Yy45MjEuNTI2IDEuNTc5IDEuMDUyIDEuOTc0IDEuNjQ0cS41OTIuNzkuNTkyIDIuMTcydjMuMDkyYzAgMS4zODEtLjUyNiAyLjEwNS0xLjUxMyAyLjEwNWMtLjUyNyAwLTEuMzgyLS4yNjMtMi41LS43OXEtNS42MjUtMi41NjUtMTIuNjMyLTIuNTY1Yy0zLjc1IDAtNi43MS41OTItOC43NSAxLjg0MnMtMy4wOTIgMy4xNTgtMy4wOTIgNS44NTVjMCAxLjg0Mi42NTggMy40MjEgMS45NzQgNC42NzFzMy43NSAyLjUgNy4yMzcgMy42MThsOS4zNDIgMi45NmM0LjczNiAxLjUxNCA4LjE1OCAzLjYxOSAxMC4xOTcgNi4zMTdzMy4wMjYgNS43ODkgMy4wMjYgOS4yMWMwIDIuODI5LS41OTIgNS4zOTUtMS43MSA3LjYzMmMtMS4xODQgMi4yMzctMi43NjMgNC4yMS00LjgwMyA1Ljc4OWMtMi4wMzkgMS42NDUtNC40NzQgMi44MjktNy4zMDIgMy42ODVjLTIuOTYxLjkyMS02LjA1MyAxLjM4MS05LjQwOCAxLjM4MSIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIwNy44MzcgMTYyLjgxNmMtMjEuNjQ1IDE1Ljk4Ny01My4wOTIgMjQuNDc0LTgwLjEzMiAyNC40NzRjLTM3Ljg5NCAwLTcyLjA0LTE0LjAxNC05Ny44MjktMzcuMzAzYy0yLjA0LTEuODQyLS4xOTctNC4zNDIgMi4yMzctMi44OTVjMjcuODk1IDE2LjE4NCA2Mi4zMDMgMjUuOTg3IDk3Ljg5NSAyNS45ODdjMjQuMDEzIDAgNTAuMzk1LTUgNzQuNjcxLTE1LjI2M2MzLjYxOC0xLjY0NSA2LjcxIDIuMzY4IDMuMTU4IDUiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIxNi44NSAxNTIuNTUzYy0yLjc2My0zLjU1My0xOC4yODktMS43MTEtMjUuMzI5LS44NTZjLTIuMTA1LjI2NC0yLjQzNC0xLjU3OS0uNTI2LTIuOTZjMTIuMzY4LTguNjg0IDMyLjY5Ny02LjE4NCAzNS4wNjYtMy4yOWMyLjM2OCAyLjk2MS0uNjU4IDIzLjI5LTEyLjIzNyAzMy4wMjdjLTEuNzc3IDEuNTEzLTMuNDg3LjcyMy0yLjY5OC0xLjI1YzIuNjMyLTYuNTEzIDguNDg3LTIxLjE4NSA1LjcyNC0yNC42NzEiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=", - resources: 142, - added: "Mar 19, 2024", - }, - { - id: 19, - account: "aws", - provider_id: 740350143862, - connected: false, - group: "Information Technology", - scan_status: "paused", - scan_status_time: "14 minutes, 45 seconds", - last_scan: "Jul 16, 2024 10:46pm UTC", - next_scan: "Jul 17, 2024 10:46pm UTC", - provider_img: - "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgdmlld0JveD0iMCAwIDI1NiAyNTYiPjxnIGZpbGw9Im5vbmUiPjxyZWN0IHdpZHRoPSIyNTYiIGhlaWdodD0iMjU2IiBmaWxsPSIjZjRmMmVkIiByeD0iNjAiLz48cGF0aCBmaWxsPSIjMjUyZjNlIiBkPSJNODQuNzQ1IDExMS45NjFjMCAyLjQzNC4yNjMgNC40MDcuNzIzIDUuODU1YTM1IDM1IDAgMCAwIDIuMTA2IDQuNzM3Yy4zMjkuNTI2LjQ2IDEuMDUyLjQ2IDEuNTEzYzAgLjY1OC0uMzk1IDEuMzE2LTEuMjUgMS45NzNsLTQuMTQ1IDIuNzY0Yy0uNTkyLjM5NC0xLjE4NC41OTItMS43MS41OTJjLS42NTggMC0xLjMxNi0uMzI5LTEuOTc0LS45MjFhMjAuNCAyMC40IDAgMCAxLTIuMzY4LTMuMDkyYTUxIDUxIDAgMCAxLTIuMDQtMy44ODJxLTcuNjk3IDkuMDgtMTkuMzQyIDkuMDc5Yy01LjUyNiAwLTkuOTM0LTEuNTc5LTEzLjE1OC00LjczN2MtMy4yMjMtMy4xNTgtNC44NjgtNy4zNjgtNC44NjgtMTIuNjMxYzAtNS41OTMgMS45NzQtMTAuMTMyIDUuOTg3LTEzLjU1M3M5LjM0Mi01LjEzMiAxNi4xMTgtNS4xMzJjMi4yMzcgMCA0LjU0LjE5OCA2Ljk3NC41MjdzNC45MzQuODU1IDcuNTY2IDEuNDQ3di00LjgwM2MwLTUtMS4wNTMtOC40ODctMy4wOTItMTAuNTI2Yy0yLjEwNi0yLjA0LTUuNjU4LTMuMDI2LTEwLjcyNC0zLjAyNmMtMi4zMDMgMC00LjY3MS4yNjMtNy4xMDUuODU1cy00LjgwMyAxLjMxNi03LjEwNiAyLjIzN2ExOSAxOSAwIDAgMS0yLjMwMi44NTVjLS40Ni4xMzItLjc5LjE5OC0xLjA1My4xOThjLS45MiAwLTEuMzgyLS42NTgtMS4zODItMi4wNHYtMy4yMjRjMC0xLjA1Mi4xMzItMS44NDIuNDYxLTIuMzAycy45MjEtLjkyMSAxLjg0Mi0xLjM4MnEzLjQ1NC0xLjc3NiA4LjI5LTIuOTZjMy4yMjMtLjg1NiA2LjY0NC0xLjI1IDEwLjI2My0xLjI1YzcuODI5IDAgMTMuNTUyIDEuNzc2IDE3LjIzNyA1LjMyOGMzLjYxOCAzLjU1MyA1LjQ2IDguOTQ4IDUuNDYgMTYuMTg1djIxLjMxNnptLTI2LjcxIDEwYzIuMTcgMCA0LjQwNy0uMzk1IDYuNzc2LTEuMTg1YzIuMzY4LS43ODkgNC40NzMtMi4yMzcgNi4yNS00LjIxYzEuMDUyLTEuMjUgMS44NDItMi42MzIgMi4yMzYtNC4yMTFzLjY1OC0zLjQ4Ny42NTgtNS43MjN2LTIuNzY0YTU1IDU1IDAgMCAwLTYuMDUyLTEuMTE4YTUwIDUwIDAgMCAwLTYuMTg1LS4zOTVjLTQuNDA4IDAtNy42MzEuODU2LTkuODAyIDIuNjMycy0zLjIyNCA0LjI3Ni0zLjIyNCA3LjU2NmMwIDMuMDkyLjc5IDUuMzk0IDIuNDM0IDYuOTczYzEuNTggMS42NDUgMy44ODIgMi40MzUgNi45MDggMi40MzVtNTIuODI4IDcuMTA1Yy0xLjE4NCAwLTEuOTc0LS4xOTgtMi41LS42NThjLS41MjYtLjM5NS0uOTg3LTEuMzE2LTEuMzgxLTIuNTY2bC0xNS40Ni01MC44NTVjLS4zOTYtMS4zMTYtLjU5My0yLjE3MS0uNTkzLTIuNjMyYzAtMS4wNTIuNTI2LTEuNjQ1IDEuNTc5LTEuNjQ1aDYuNDQ3YzEuMjUgMCAyLjEwNi4xOTggMi41NjYuNjU4Yy41MjYuMzk1LjkyMSAxLjMxNiAxLjMxNiAyLjU2NmwxMS4wNTIgNDMuNTUzbDEwLjI2NC00My41NTNjLjMyOS0xLjMxNi43MjMtMi4xNyAxLjI1LTIuNTY2Yy41MjYtLjM5NCAxLjQ0Ny0uNjU3IDIuNjMxLS42NTdoNS4yNjNjMS4yNSAwIDIuMTA2LjE5NyAyLjYzMi42NTdjLjUyNi4zOTUuOTg3IDEuMzE2IDEuMjUgMi41NjZsMTAuMzk1IDQ0LjA3OWwxMS4zODEtNDQuMDc5Yy4zOTUtMS4zMTYuODU2LTIuMTcgMS4zMTYtMi41NjZjLjUyNi0uMzk0IDEuMzgyLS42NTcgMi41NjYtLjY1N2g2LjExOGMxLjA1MyAwIDEuNjQ1LjUyNiAxLjY0NSAxLjY0NGMwIC4zMy0uMDY2LjY1OC0uMTMyIDEuMDUzYy0uMDY1LjM5NS0uMTk3LjkyLS40NiAxLjY0NWwtMTUuODU1IDUwLjg1NXEtLjU5MyAxLjk3NC0xLjM4MiAyLjU2NmMtLjUyNi4zOTQtMS4zODIuNjU4LTIuNS42NThoLTUuNjU4Yy0xLjI1IDAtMi4xMDUtLjE5OC0yLjYzMS0uNjU4Yy0uNTI3LS40NjEtLjk4Ny0xLjMxNi0xLjI1LTIuNjMybC0xMC4xOTgtNDIuNDM0bC0xMC4xMzEgNDIuMzY4Yy0uMzI5IDEuMzE2LS43MjQgMi4xNzEtMS4yNSAyLjYzMmMtLjUyNy40Ni0xLjQ0OC42NTgtMi42MzIuNjU4em04NC41NCAxLjc3NmMtMy40MjEgMC02Ljg0Mi0uMzk1LTEwLjEzMi0xLjE4NGMtMy4yODktLjc5LTUuODU1LTEuNjQ1LTcuNTY2LTIuNjMyYy0xLjA1Mi0uNTkyLTEuNzc2LTEuMjUtMi4wMzktMS44NDJhNC42NSA0LjY1IDAgMCAxLS4zOTUtMS44NDJ2LTMuMzU1YzAtMS4zODIuNTI2LTIuMDQgMS41MTMtMi4wNHEuNTkzIDAgMS4xODQuMTk4Yy4zOTUuMTMxLjk4Ny4zOTQgMS42NDUuNjU4YTM1LjggMzUuOCAwIDAgMCA3LjIzNyAyLjMwMmEzOS41IDM5LjUgMCAwIDAgNy44MjkuNzljNC4xNDUgMCA3LjM2OC0uNzI0IDkuNjA1LTIuMTcxYzIuMjM3LTEuNDQ4IDMuNDIxLTMuNTUzIDMuNDIxLTYuMjVjMC0xLjg0Mi0uNTkyLTMuMzU2LTEuNzc2LTQuNjA2cy0zLjQyMS0yLjM2OC02LjY0NS0zLjQyMWwtOS41MzktMi45NmMtNC44MDMtMS41MTMtOC4zNTYtMy43NS0xMC41MjctNi43MWMtMi4xNzEtMi44OTUtMy4yODktNi4xMi0zLjI4OS05LjU0cTAtNC4xNDQgMS43NzYtNy4zMDNjMS4xODQtMi4xMDUgMi43NjMtMy45NDcgNC43MzctNS4zOTRjMS45NzQtMS41MTQgNC4yMTEtMi42MzIgNi44NDItMy40MjJjMi42MzItLjc5IDUuMzk1LTEuMTE4IDguMjktMS4xMThjMS40NDcgMCAyLjk2LjA2NiA0LjQwOC4yNjNjMS41MTMuMTk3IDIuODk0LjQ2IDQuMjc2LjcyNGMxLjMxNi4zMjkgMi41NjYuNjU4IDMuNzUgMS4wNTNxMS43NzYuNTkxIDIuNzYzIDEuMTg0Yy45MjEuNTI2IDEuNTc5IDEuMDUyIDEuOTc0IDEuNjQ0cS41OTIuNzkuNTkyIDIuMTcydjMuMDkyYzAgMS4zODEtLjUyNiAyLjEwNS0xLjUxMyAyLjEwNWMtLjUyNyAwLTEuMzgyLS4yNjMtMi41LS43OXEtNS42MjUtMi41NjUtMTIuNjMyLTIuNTY1Yy0zLjc1IDAtNi43MS41OTItOC43NSAxLjg0MnMtMy4wOTIgMy4xNTgtMy4wOTIgNS44NTVjMCAxLjg0Mi42NTggMy40MjEgMS45NzQgNC42NzFzMy43NSAyLjUgNy4yMzcgMy42MThsOS4zNDIgMi45NmM0LjczNiAxLjUxNCA4LjE1OCAzLjYxOSAxMC4xOTcgNi4zMTdzMy4wMjYgNS43ODkgMy4wMjYgOS4yMWMwIDIuODI5LS41OTIgNS4zOTUtMS43MSA3LjYzMmMtMS4xODQgMi4yMzctMi43NjMgNC4yMS00LjgwMyA1Ljc4OWMtMi4wMzkgMS42NDUtNC40NzQgMi44MjktNy4zMDIgMy42ODVjLTIuOTYxLjkyMS02LjA1MyAxLjM4MS05LjQwOCAxLjM4MSIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIwNy44MzcgMTYyLjgxNmMtMjEuNjQ1IDE1Ljk4Ny01My4wOTIgMjQuNDc0LTgwLjEzMiAyNC40NzRjLTM3Ljg5NCAwLTcyLjA0LTE0LjAxNC05Ny44MjktMzcuMzAzYy0yLjA0LTEuODQyLS4xOTctNC4zNDIgMi4yMzctMi44OTVjMjcuODk1IDE2LjE4NCA2Mi4zMDMgMjUuOTg3IDk3Ljg5NSAyNS45ODdjMjQuMDEzIDAgNTAuMzk1LTUgNzQuNjcxLTE1LjI2M2MzLjYxOC0xLjY0NSA2LjcxIDIuMzY4IDMuMTU4IDUiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIxNi44NSAxNTIuNTUzYy0yLjc2My0zLjU1My0xOC4yODktMS43MTEtMjUuMzI5LS44NTZjLTIuMTA1LjI2NC0yLjQzNC0xLjU3OS0uNTI2LTIuOTZjMTIuMzY4LTguNjg0IDMyLjY5Ny02LjE4NCAzNS4wNjYtMy4yOWMyLjM2OCAyLjk2MS0uNjU4IDIzLjI5LTEyLjIzNyAzMy4wMjdjLTEuNzc3IDEuNTEzLTMuNDg3LjcyMy0yLjY5OC0xLjI1YzIuNjMyLTYuNTEzIDguNDg3LTIxLjE4NSA1LjcyNC0yNC42NzEiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=", - resources: 130, - added: "Feb 23, 2024", - }, - { - id: 20, - account: "aws", - provider_id: 740350143863, - connected: true, - group: "Operations", - scan_status: "active", - scan_status_time: "7 minutes, 20 seconds", - last_scan: "Jul 16, 2024 9:46pm UTC", - next_scan: "Jul 17, 2024 9:46pm UTC", - provider_img: - "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0OCIgaGVpZ2h0PSI0OCIgdmlld0JveD0iMCAwIDI1NiAyNTYiPjxnIGZpbGw9Im5vbmUiPjxyZWN0IHdpZHRoPSIyNTYiIGhlaWdodD0iMjU2IiBmaWxsPSIjZjRmMmVkIiByeD0iNjAiLz48cGF0aCBmaWxsPSIjMjUyZjNlIiBkPSJNODQuNzQ1IDExMS45NjFjMCAyLjQzNC4yNjMgNC40MDcuNzIzIDUuODU1YTM1IDM1IDAgMCAwIDIuMTA2IDQuNzM3Yy4zMjkuNTI2LjQ2IDEuMDUyLjQ2IDEuNTEzYzAgLjY1OC0uMzk1IDEuMzE2LTEuMjUgMS45NzNsLTQuMTQ1IDIuNzY0Yy0uNTkyLjM5NC0xLjE4NC41OTItMS43MS41OTJjLS42NTggMC0xLjMxNi0uMzI5LTEuOTc0LS45MjFhMjAuNCAyMC40IDAgMCAxLTIuMzY4LTMuMDkyYTUxIDUxIDAgMCAxLTIuMDQtMy44ODJxLTcuNjk3IDkuMDgtMTkuMzQyIDkuMDc5Yy01LjUyNiAwLTkuOTM0LTEuNTc5LTEzLjE1OC00LjczN2MtMy4yMjMtMy4xNTgtNC44NjgtNy4zNjgtNC44NjgtMTIuNjMxYzAtNS41OTMgMS45NzQtMTAuMTMyIDUuOTg3LTEzLjU1M3M5LjM0Mi01LjEzMiAxNi4xMTgtNS4xMzJjMi4yMzcgMCA0LjU0LjE5OCA2Ljk3NC41MjdzNC45MzQuODU1IDcuNTY2IDEuNDQ3di00LjgwM2MwLTUtMS4wNTMtOC40ODctMy4wOTItMTAuNTI2Yy0yLjEwNi0yLjA0LTUuNjU4LTMuMDI2LTEwLjcyNC0zLjAyNmMtMi4zMDMgMC00LjY3MS4yNjMtNy4xMDUuODU1cy00LjgwMyAxLjMxNi03LjEwNiAyLjIzN2ExOSAxOSAwIDAgMS0yLjMwMi44NTVjLS40Ni4xMzItLjc5LjE5OC0xLjA1My4xOThjLS45MiAwLTEuMzgyLS42NTgtMS4zODItMi4wNHYtMy4yMjRjMC0xLjA1Mi4xMzItMS44NDIuNDYxLTIuMzAycy45MjEtLjkyMSAxLjg0Mi0xLjM4MnEzLjQ1NC0xLjc3NiA4LjI5LTIuOTZjMy4yMjMtLjg1NiA2LjY0NC0xLjI1IDEwLjI2My0xLjI1YzcuODI5IDAgMTMuNTUyIDEuNzc2IDE3LjIzNyA1LjMyOGMzLjYxOCAzLjU1MyA1LjQ2IDguOTQ4IDUuNDYgMTYuMTg1djIxLjMxNnptLTI2LjcxIDEwYzIuMTcgMCA0LjQwNy0uMzk1IDYuNzc2LTEuMTg1YzIuMzY4LS43ODkgNC40NzMtMi4yMzcgNi4yNS00LjIxYzEuMDUyLTEuMjUgMS44NDItMi42MzIgMi4yMzYtNC4yMTFzLjY1OC0zLjQ4Ny42NTgtNS43MjN2LTIuNzY0YTU1IDU1IDAgMCAwLTYuMDUyLTEuMTE4YTUwIDUwIDAgMCAwLTYuMTg1LS4zOTVjLTQuNDA4IDAtNy42MzEuODU2LTkuODAyIDIuNjMycy0zLjIyNCA0LjI3Ni0zLjIyNCA3LjU2NmMwIDMuMDkyLjc5IDUuMzk0IDIuNDM0IDYuOTczYzEuNTggMS42NDUgMy44ODIgMi40MzUgNi45MDggMi40MzVtNTIuODI4IDcuMTA1Yy0xLjE4NCAwLTEuOTc0LS4xOTgtMi41LS42NThjLS41MjYtLjM5NS0uOTg3LTEuMzE2LTEuMzgxLTIuNTY2bC0xNS40Ni01MC44NTVjLS4zOTYtMS4zMTYtLjU5My0yLjE3MS0uNTkzLTIuNjMyYzAtMS4wNTIuNTI2LTEuNjQ1IDEuNTc5LTEuNjQ1aDYuNDQ3YzEuMjUgMCAyLjEwNi4xOTggMi41NjYuNjU4Yy41MjYuMzk1LjkyMSAxLjMxNiAxLjMxNiAyLjU2NmwxMS4wNTIgNDMuNTUzbDEwLjI2NC00My41NTNjLjMyOS0xLjMxNi43MjMtMi4xNyAxLjI1LTIuNTY2Yy41MjYtLjM5NCAxLjQ0Ny0uNjU3IDIuNjMxLS42NTdoNS4yNjNjMS4yNSAwIDIuMTA2LjE5NyAyLjYzMi42NTdjLjUyNi4zOTUuOTg3IDEuMzE2IDEuMjUgMi41NjZsMTAuMzk1IDQ0LjA3OWwxMS4zODEtNDQuMDc5Yy4zOTUtMS4zMTYuODU2LTIuMTcgMS4zMTYtMi41NjZjLjUyNi0uMzk0IDEuMzgyLS42NTcgMi41NjYtLjY1N2g2LjExOGMxLjA1MyAwIDEuNjQ1LjUyNiAxLjY0NSAxLjY0NGMwIC4zMy0uMDY2LjY1OC0uMTMyIDEuMDUzYy0uMDY1LjM5NS0uMTk3LjkyLS40NiAxLjY0NWwtMTUuODU1IDUwLjg1NXEtLjU5MyAxLjk3NC0xLjM4MiAyLjU2NmMtLjUyNi4zOTQtMS4zODIuNjU4LTIuNS42NThoLTUuNjU4Yy0xLjI1IDAtMi4xMDUtLjE5OC0yLjYzMS0uNjU4Yy0uNTI3LS40NjEtLjk4Ny0xLjMxNi0xLjI1LTIuNjMybC0xMC4xOTgtNDIuNDM0bC0xMC4xMzEgNDIuMzY4Yy0uMzI5IDEuMzE2LS43MjQgMi4xNzEtMS4yNSAyLjYzMmMtLjUyNy40Ni0xLjQ0OC42NTgtMi42MzIuNjU4em04NC41NCAxLjc3NmMtMy40MjEgMC02Ljg0Mi0uMzk1LTEwLjEzMi0xLjE4NGMtMy4yODktLjc5LTUuODU1LTEuNjQ1LTcuNTY2LTIuNjMyYy0xLjA1Mi0uNTkyLTEuNzc2LTEuMjUtMi4wMzktMS44NDJhNC42NSA0LjY1IDAgMCAxLS4zOTUtMS44NDJ2LTMuMzU1YzAtMS4zODIuNTI2LTIuMDQgMS41MTMtMi4wNHEuNTkzIDAgMS4xODQuMTk4Yy4zOTUuMTMxLjk4Ny4zOTQgMS42NDUuNjU4YTM1LjggMzUuOCAwIDAgMCA3LjIzNyAyLjMwMmEzOS41IDM5LjUgMCAwIDAgNy44MjkuNzljNC4xNDUgMCA3LjM2OC0uNzI0IDkuNjA1LTIuMTcxYzIuMjM3LTEuNDQ4IDMuNDIxLTMuNTUzIDMuNDIxLTYuMjVjMC0xLjg0Mi0uNTkyLTMuMzU2LTEuNzc2LTQuNjA2cy0zLjQyMS0yLjM2OC02LjY0NS0zLjQyMWwtOS41MzktMi45NmMtNC44MDMtMS41MTMtOC4zNTYtMy43NS0xMC41MjctNi43MWMtMi4xNzEtMi44OTUtMy4yODktNi4xMi0zLjI4OS05LjU0cTAtNC4xNDQgMS43NzYtNy4zMDNjMS4xODQtMi4xMDUgMi43NjMtMy45NDcgNC43MzctNS4zOTRjMS45NzQtMS41MTQgNC4yMTEtMi42MzIgNi44NDItMy40MjJjMi42MzItLjc5IDUuMzk1LTEuMTE4IDguMjktMS4xMThjMS40NDcgMCAyLjk2LjA2NiA0LjQwOC4yNjNjMS41MTMuMTk3IDIuODk0LjQ2IDQuMjc2LjcyNGMxLjMxNi4zMjkgMi41NjYuNjU4IDMuNzUgMS4wNTNxMS43NzYuNTkxIDIuNzYzIDEuMTg0Yy45MjEuNTI2IDEuNTc5IDEuMDUyIDEuOTc0IDEuNjQ0cS41OTIuNzkuNTkyIDIuMTcydjMuMDkyYzAgMS4zODEtLjUyNiAyLjEwNS0xLjUxMyAyLjEwNWMtLjUyNyAwLTEuMzgyLS4yNjMtMi41LS43OXEtNS42MjUtMi41NjUtMTIuNjMyLTIuNTY1Yy0zLjc1IDAtNi43MS41OTItOC43NSAxLjg0MnMtMy4wOTIgMy4xNTgtMy4wOTIgNS44NTVjMCAxLjg0Mi42NTggMy40MjEgMS45NzQgNC42NzFzMy43NSAyLjUgNy4yMzcgMy42MThsOS4zNDIgMi45NmM0LjczNiAxLjUxNCA4LjE1OCAzLjYxOSAxMC4xOTcgNi4zMTdzMy4wMjYgNS43ODkgMy4wMjYgOS4yMWMwIDIuODI5LS41OTIgNS4zOTUtMS43MSA3LjYzMmMtMS4xODQgMi4yMzctMi43NjMgNC4yMS00LjgwMyA1Ljc4OWMtMi4wMzkgMS42NDUtNC40NzQgMi44MjktNy4zMDIgMy42ODVjLTIuOTYxLjkyMS02LjA1MyAxLjM4MS05LjQwOCAxLjM4MSIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIwNy44MzcgMTYyLjgxNmMtMjEuNjQ1IDE1Ljk4Ny01My4wOTIgMjQuNDc0LTgwLjEzMiAyNC40NzRjLTM3Ljg5NCAwLTcyLjA0LTE0LjAxNC05Ny44MjktMzcuMzAzYy0yLjA0LTEuODQyLS4xOTctNC4zNDIgMi4yMzctMi44OTVjMjcuODk1IDE2LjE4NCA2Mi4zMDMgMjUuOTg3IDk3Ljg5NSAyNS45ODdjMjQuMDEzIDAgNTAuMzk1LTUgNzQuNjcxLTE1LjI2M2MzLjYxOC0xLjY0NSA2LjcxIDIuMzY4IDMuMTU4IDUiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjxwYXRoIGZpbGw9IiNmOTAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTIxNi44NSAxNTIuNTUzYy0yLjc2My0zLjU1My0xOC4yODktMS43MTEtMjUuMzI5LS44NTZjLTIuMTA1LjI2NC0yLjQzNC0xLjU3OS0uNTI2LTIuOTZjMTIuMzY4LTguNjg0IDMyLjY5Ny02LjE4NCAzNS4wNjYtMy4yOWMyLjM2OCAyLjk2MS0uNjU4IDIzLjI5LTEyLjIzNyAzMy4wMjdjLTEuNzc3IDEuNTEzLTMuNDg3LjcyMy0yLjY5OC0xLjI1YzIuNjMyLTYuNTEzIDguNDg3LTIxLjE4NSA1LjcyNC0yNC42NzEiIGNsaXAtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=", - resources: 132, - added: "Apr 1, 2024", - }, -]; diff --git a/app/(prowler)/providers/page.tsx b/app/(prowler)/providers/page.tsx index d3cd8c1611..2301d26738 100644 --- a/app/(prowler)/providers/page.tsx +++ b/app/(prowler)/providers/page.tsx @@ -1,20 +1,11 @@ import { Spacer } from "@nextui-org/react"; import React from "react"; -import { CustomTable, Header, ModalWrap } from "@/components"; +import { ColumnsProviders, DataTable, Header, ModalWrap } from "@/components"; +import { getProvider } from "@/lib/actions"; -const visibleColumns: string[] = [ - "account", - "group", - "scan_status", - "last_scan", - "next_scan", - "resources", - "added", - "actions", -]; - -export default function Providers() { +export default async function Providers() { + const providers = await getProvider(); const onSave = async () => { "use server"; // event we want to pass down, ex. console.log("### hello"); @@ -24,23 +15,21 @@ export default function Providers() { <>
- - - -

Modal body content

- - } - actionButtonLabel="Save" - onAction={onSave} - openButtonLabel="Open Modal" - /> +
+ +

Modal body content

+ + } + actionButtonLabel="Save" + onAction={onSave} + openButtonLabel="Add Cloud Accounts" + /> + + +
); } diff --git a/app/(prowler)/settings/page.tsx b/app/(prowler)/settings/page.tsx new file mode 100644 index 0000000000..526b8c08df --- /dev/null +++ b/app/(prowler)/settings/page.tsx @@ -0,0 +1,12 @@ +import { Spacer } from "@nextui-org/react"; + +import { Header } from "@/components"; + +export default async function Settings() { + return ( + <> +
+ + + ); +} diff --git a/app/api/providers/route.ts b/app/api/providers/route.ts new file mode 100644 index 0000000000..ef9b5995e2 --- /dev/null +++ b/app/api/providers/route.ts @@ -0,0 +1,10 @@ +import { NextResponse } from "next/server"; + +import data from "../../../dataProviders.json"; + +export async function GET() { + // Simulate fetching data with a delay + await new Promise((resolve) => setTimeout(resolve, 2000)); + + return NextResponse.json({ providers: data }); +} diff --git a/components/index.ts b/components/index.ts index 0d1a39d806..faa5b19c57 100644 --- a/components/index.ts +++ b/components/index.ts @@ -1,7 +1,10 @@ -export * from "./providers/AccountInfo"; export * from "./providers/DateWithTime"; +export * from "./providers/ProviderInfo"; export * from "./providers/ScanStatus"; +export * from "./providers/table/ColumnsProviders"; +export * from "./providers/table/DataTable"; export * from "./ui/header/Header"; export * from "./ui/modal"; export * from "./ui/sidebar"; -export * from "./ui/table/CustomTable"; +export * from "./ui/table/StatusBadge"; +export * from "./ui/table/Table"; diff --git a/components/providers/DateWithTime.tsx b/components/providers/DateWithTime.tsx index 9895483e31..1e968fae3f 100644 --- a/components/providers/DateWithTime.tsx +++ b/components/providers/DateWithTime.tsx @@ -16,7 +16,7 @@ export const DateWithTime: React.FC = ({ return (
-
+
{formattedDate} {showTime && ( {formattedTime} diff --git a/components/providers/AccountInfo.tsx b/components/providers/ProviderInfo.tsx similarity index 76% rename from components/providers/AccountInfo.tsx rename to components/providers/ProviderInfo.tsx index 7cab849d54..bc1e90124a 100644 --- a/components/providers/AccountInfo.tsx +++ b/components/providers/ProviderInfo.tsx @@ -8,18 +8,18 @@ import { WifiOffIcon, } from "../icons"; -interface AccountInfoProps { +interface ProviderInfoProps { connected: boolean; provider: "aws" | "azure" | "gcp"; - accountName: string; - accountId: string; + providerAlias: string; + providerId: string; } -export const AccountInfo: React.FC = ({ +export const ProviderInfo: React.FC = ({ connected, provider, - accountName, - accountId, + providerAlias, + providerId, }) => { const getIcon = () => { return connected ? : ; @@ -45,8 +45,8 @@ export const AccountInfo: React.FC = ({
{getIcon()}
{getProviderLogo()}
- {accountName} - {accountId} + {providerAlias} + {providerId}
diff --git a/components/providers/table/ColumnsProviders.tsx b/components/providers/table/ColumnsProviders.tsx new file mode 100644 index 0000000000..3701b68e2a --- /dev/null +++ b/components/providers/table/ColumnsProviders.tsx @@ -0,0 +1,110 @@ +"use client"; + +import { + Button, + Dropdown, + DropdownItem, + DropdownMenu, + DropdownTrigger, +} from "@nextui-org/react"; +import { ColumnDef } from "@tanstack/react-table"; +import { add } from "date-fns"; + +import { VerticalDotsIcon } from "@/components/icons"; +import { StatusBadge } from "@/components/ui/table/StatusBadge"; +import { ProviderProps } from "@/types"; + +import { DateWithTime } from "../DateWithTime"; +import { ProviderInfo } from "../ProviderInfo"; + +const getProviderAttributes = (row: { original: ProviderProps }) => { + return row.original.attributes; +}; + +export const ColumnsProviders: ColumnDef[] = [ + { + header: "ID", + cell: ({ row }) =>

{row.index + 1}

, + }, + { + accessorKey: "account", + header: "Account", + cell: ({ row }) => { + const { connection, provider, alias, provider_id } = + getProviderAttributes(row); + return ( + + ); + }, + }, + { + accessorKey: "status", + header: "Scan Status", + cell: ({ row }) => { + const { status } = getProviderAttributes(row); + return ; + }, + }, + { + accessorKey: "lastScan", + header: "Last Scan", + cell: ({ row }) => { + const { updated_at } = getProviderAttributes(row); + return ; + }, + }, + { + accessorKey: "nextScan", + header: "Next Scan", + cell: ({ row }) => { + const { updated_at } = getProviderAttributes(row); + const nextDay = add(new Date(updated_at), { + hours: 24, + }); + return ; + }, + }, + { + accessorKey: "resources", + header: "Resources", + cell: ({ row }) => { + const { resources } = getProviderAttributes(row); + return

{resources}

; + }, + }, + { + accessorKey: "added", + header: "Added", + cell: ({ row }) => { + const { inserted_at } = getProviderAttributes(row); + return ; + }, + }, + { + accessorKey: "actions", + header: () =>
Actions
, + id: "actions", + cell: () => { + return ( +
+ + + + + + Manage + Delete + + +
+ ); + }, + }, +]; diff --git a/components/providers/table/DataTable.tsx b/components/providers/table/DataTable.tsx new file mode 100644 index 0000000000..cfcf74ff2a --- /dev/null +++ b/components/providers/table/DataTable.tsx @@ -0,0 +1,109 @@ +"use client"; + +import { Button } from "@nextui-org/react"; +import { + ColumnDef, + flexRender, + getCoreRowModel, + getPaginationRowModel, + useReactTable, +} from "@tanstack/react-table"; + +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table/Table"; + +interface DataTableProps { + columns: ColumnDef[]; + data: TData[]; +} + +export function DataTable({ + columns, + data, +}: DataTableProps) { + const table = useReactTable({ + data, + columns, + getCoreRowModel: getCoreRowModel(), + getPaginationRowModel: getPaginationRowModel(), + }); + + return ( + <> +
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext(), + )} + + ); + })} + + ))} + + + {table.getRowModel().rows?.length ? ( + table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender( + cell.column.columnDef.cell, + cell.getContext(), + )} + + ))} + + )) + ) : ( + + + No results. + + + )} + +
+
+
+ + +
+ + ); +} diff --git a/components/ui/sidebar/SidebarWrap.tsx b/components/ui/sidebar/SidebarWrap.tsx index f2212850bb..325e284f15 100644 --- a/components/ui/sidebar/SidebarWrap.tsx +++ b/components/ui/sidebar/SidebarWrap.tsx @@ -4,9 +4,11 @@ import { Icon } from "@iconify/react"; import { Button, ScrollShadow, Spacer, Tooltip } from "@nextui-org/react"; import clsx from "clsx"; import { usePathname } from "next/navigation"; -import React, { useCallback, useEffect, useState } from "react"; +import React, { useCallback } from "react"; import { useMediaQuery } from "usehooks-ts"; +import { useLocalStorage } from "@/hooks/useLocalStorage"; + import { ProwlerExtended, ProwlerShort, @@ -17,33 +19,18 @@ import { sectionItemsWithTeams } from "./SidebarItems"; import { UserAvatar } from "./UserAvatar"; export const SidebarWrap = () => { - const [isCollapsed, setIsCollapsed] = useState(false); - const [isLoaded, setIsLoaded] = useState(false); - const pathname = usePathname(); - const currentPath = pathname === "/" ? "overview" : pathname.split("/")?.[1]; + const [isCollapsed, setIsCollapsed] = useLocalStorage("isCollapsed", false); const isMobile = useMediaQuery("(max-width: 768px)"); - const isCompact = isCollapsed || isMobile; - - useEffect(() => { - const savedState = localStorage.getItem("isCollapsed"); - if (savedState !== null) { - setIsCollapsed(JSON.parse(savedState)); - } - setIsLoaded(true); - }, []); + const isCompact = Boolean(isCollapsed) || isMobile; const onToggle = useCallback(() => { - setIsCollapsed((prev) => { - const newState = !prev; - localStorage.setItem("isCollapsed", JSON.stringify(newState)); - return newState; - }); - }, []); + setIsCollapsed(!isCollapsed); + }, [isCollapsed]); - if (!isLoaded) return null; + const currentPath = pathname === "/" ? "overview" : pathname.split("/")?.[1]; return (
= ({ - initialVisibleColumns, - initialRowsPerPage = 5, - selectionMode = "none", -}) => { - const [filterValue, setFilterValue] = React.useState(""); - // const [selectedKeys, setSelectedKeys] = React.useState(new Set([])); - const selectedKeys = new Set([]); - - // const [visibleColumns, setVisibleColumns] = React.useState< - // string | Set - // >(new Set(initialVisibleColumns)); - - const visibleColumns = new Set(initialVisibleColumns); - - // const [statusFilter, setStatusFilter] = React.useState("all"); - const statusFilter: string = "all"; - - const [rowsPerPage, setRowsPerPage] = React.useState(initialRowsPerPage); - - // const [sortDescriptor, setSortDescriptor] = React.useState({ - // column: "age", - // direction: "ascending", - // }); - - const sortDescriptor: SortDescriptor = { - column: "age", - direction: "ascending", - }; - - const [page, setPage] = React.useState(1); - - const pages = Math.ceil(providers.length / rowsPerPage); - - const hasSearchFilter = Boolean(filterValue); - - const headerColumns = React.useMemo(() => { - // if (visibleColumns === "all") return columns; - - return columns.filter((column) => - Array.from(visibleColumns).includes(column.uid), - ); - }, [visibleColumns]); - - const filteredItems = React.useMemo(() => { - let filteredUsers = [...providers]; - - if (hasSearchFilter) { - filteredUsers = filteredUsers.filter((provider) => - provider.account.toLowerCase().includes(filterValue.toLowerCase()), - ); - } - if ( - statusFilter !== "all" && - Array.from(statusFilter).length !== statusOptions.length - ) { - filteredUsers = filteredUsers.filter((provider) => - Array.from(statusFilter).includes(provider.group), - ); - } - - return filteredUsers; - }, [providers, filterValue, statusFilter]); - - const items = React.useMemo(() => { - const start = (page - 1) * rowsPerPage; - const end = start + rowsPerPage; - - return filteredItems.slice(start, end); - }, [page, filteredItems, rowsPerPage]); - - const sortedItems = React.useMemo(() => { - // The type "any" for a and b will removed in the next iteration. - return [...items].sort((a: any, b: any) => { - const first = a[sortDescriptor.column]; - const second = b[sortDescriptor.column]; - const cmp: number = first < second ? -1 : first > second ? 1 : 0; - - return sortDescriptor.direction === "descending" ? -cmp : cmp; - }); - }, [sortDescriptor, items]); - - const renderCell = React.useCallback( - (provider: Provider, columnKey: keyof Provider) => { - // eslint-disable-next-line security/detect-object-injection - const cellValue = provider[columnKey]; - - switch (columnKey) { - case "account": - return ( - - ); - case "group": - return ( -
-

{cellValue}

-

- {provider.group} -

-
- ); - case "scan_status": - return ( - - {cellValue} - - ); - case "actions": - return ( -
- - - - - - Manage - Delete - - -
- ); - default: - return cellValue; - } - }, - [], - ); - - const onRowsPerPageChange = React.useCallback( - (e: React.ChangeEvent) => { - setRowsPerPage(Number(e.target.value)); - setPage(1); - }, - [], - ); - - const onSearchChange = React.useCallback((value: string) => { - if (value) { - setFilterValue(value); - setPage(1); - } else { - setFilterValue(""); - } - }, []); - - const topContent = React.useMemo(() => { - return ( -
-
- {/* } - value={filterValue} - variant="bordered" - onClear={() => setFilterValue("")} - onValueChange={onSearchChange} - /> */} -
- {/* - - - - - {statusOptions.map((status) => ( - - {capitalize(status.name)} - - ))} - - - - - - - - {columns.map((column) => ( - - {capitalize(column.name)} - - ))} - - */} - -
-
-
- - Total {providers.length} entries - - {items.length < initialRowsPerPage &&
hi hi que pasa
} - -
-
- ); - }, [ - filterValue, - statusFilter, - visibleColumns, - onSearchChange, - onRowsPerPageChange, - providers.length, - hasSearchFilter, - ]); - - const bottomContent = React.useMemo(() => { - return ( -
- - {/* {selectionMode !== "none" && ( - - {selectedKeys === "all" - ? "All items selected" - : `${selectedKeys.size} of ${items.length} selected`} - - )} */} -
- ); - }, [selectedKeys, items.length, page, pages, hasSearchFilter]); - - const classNames = React.useMemo( - () => ({ - wrapper: ["max-h-[382px]", "max-w-3xl"], - th: ["bg-transparent", "text-default-500", "border-b", "border-divider"], - td: [ - // changing the rows border radius - // first - "group-data-[first=true]:first:before:rounded-none", - "group-data-[first=true]:last:before:rounded-none", - // middle - "group-data-[middle=true]:before:rounded-none", - // last - "group-data-[last=true]:first:before:rounded-none", - "group-data-[last=true]:last:before:rounded-none", - ], - }), - [], - ); - - return ( - - - {(column) => ( - - {column.name} - - )} - - - {(item) => { - return ( - - {(columnKey) => ( - // @ts-expect-error: Disable type checking for this line - {renderCell(item, columnKey)} - )} - - ); - }} - -
- ); -}; diff --git a/components/ui/table/StatusBadge.tsx b/components/ui/table/StatusBadge.tsx new file mode 100644 index 0000000000..a1dea200aa --- /dev/null +++ b/components/ui/table/StatusBadge.tsx @@ -0,0 +1,25 @@ +import { Chip } from "@nextui-org/react"; +import React from "react"; + +type Status = "completed" | "pending" | "cancelled"; + +export const statusColorMap: Record = + { + completed: "success", + pending: "warning", + cancelled: "danger", + }; + +export const StatusBadge = ({ status }: { status: Status }) => { + return ( + + {status} + + ); +}; diff --git a/components/ui/table/Table.tsx b/components/ui/table/Table.tsx new file mode 100644 index 0000000000..cdc1f5cfe6 --- /dev/null +++ b/components/ui/table/Table.tsx @@ -0,0 +1,117 @@ +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +const Table = React.forwardRef< + HTMLTableElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+ + +)); +Table.displayName = "Table"; + +const TableHeader = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)); +TableHeader.displayName = "TableHeader"; + +const TableBody = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)); +TableBody.displayName = "TableBody"; + +const TableFooter = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + tr]:last:border-b-0 dark:bg-slate-800/50", + className, + )} + {...props} + /> +)); +TableFooter.displayName = "TableFooter"; + +const TableRow = React.forwardRef< + HTMLTableRowElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)); +TableRow.displayName = "TableRow"; + +const TableHead = React.forwardRef< + HTMLTableCellElement, + React.ThHTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +TableHead.displayName = "TableHead"; + +const TableCell = React.forwardRef< + HTMLTableCellElement, + React.TdHTMLAttributes +>(({ className, ...props }, ref) => ( + +)); +TableCell.displayName = "TableCell"; + +const TableCaption = React.forwardRef< + HTMLTableCaptionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +TableCaption.displayName = "TableCaption"; + +export { + Table, + TableBody, + TableCaption, + TableCell, + TableFooter, + TableHead, + TableHeader, + TableRow, +}; diff --git a/config/fonts.ts b/config/fonts.ts index 0e7d9c9422..16c934d5a6 100644 --- a/config/fonts.ts +++ b/config/fonts.ts @@ -1,4 +1,7 @@ -import { Fira_Code as FontMono, Inter as FontSans } from "next/font/google"; +import { + Fira_Code as FontMono, + Plus_Jakarta_Sans as FontSans, +} from "next/font/google"; export const fontSans = FontSans({ subsets: ["latin"], diff --git a/dataProviders.json b/dataProviders.json new file mode 100644 index 0000000000..ab8a058620 --- /dev/null +++ b/dataProviders.json @@ -0,0 +1,105 @@ +{ + "links": { + "first": "https://api.prowler.com/api/v1/providers?page%5Bnumber%5D=1", + "last": "https://api.prowler.com/api/v1/providers?page%5Bnumber%5D=1", + "next": null, + "prev": null + }, + "data": [ + { + "id": "5fd8f121-269e-4715-84cf-f92373f15dfa", + "type": "providers", + "attributes": { + "provider": "aws", + "provider_id": "1234567890", + "alias": "mock_aws_connected", + "status": "cancelled", + "resources": 101, + "connection": { + "connected": true, + "last_checked_at": "2024-07-17T09:55:14.191475Z" + }, + "scanner_args": { + "only_logs": true, + "excluded_checks": [ + "awslambda_function_no_secrets_in_code", + "cloudwatch_log_group_no_secrets_in_logs" + ], + "aws_retries_max_attempts": 5 + }, + "inserted_at": "2024-07-17T09:55:14.191475Z", + "updated_at": "2024-07-17T09:55:14.191475Z", + "created_by": { + "object": "user", + "id": "eea048ab-7cb3-47eb-9e5e-dce591ade41f" + } + } + }, + { + "id": "16aaeb4e-d3cd-4bb6-86f8-6c39cf93821e", + "type": "providers", + "attributes": { + "provider": "azure", + "provider_id": "1234567891", + "alias": "mock_aws_not_connected", + "status": "pending", + "resources": 222, + "connection": { + "connected": false, + "last_checked_at": "2024-07-17T09:55:18.987425Z" + }, + "scanner_args": { + "only_logs": true, + "excluded_checks": [ + "awslambda_function_no_secrets_in_code", + "cloudwatch_log_group_no_secrets_in_logs" + ], + "aws_retries_max_attempts": 5 + }, + "inserted_at": "2024-07-17T09:50:18.987425Z", + "updated_at": "2024-07-17T09:55:18.987425Z", + "created_by": { + "object": "user", + "id": "a8f5e964-5964-4aaf-9176-844e2c3b0716" + } + } + }, + { + "id": "63f16b03-7849-4054-b40b-300e331f46f0", + "type": "providers", + "attributes": { + "provider": "gcp", + "provider_id": "1234567895", + "alias": "mock_gcp", + "status": "completed", + "resources": 143, + "connection": { + "connected": true, + "last_checked_at": "2024-07-17T09:55:18.987425Z" + }, + "scanner_args": { + "only_logs": true, + "excluded_checks": [ + "apikeys_key_exists", + "cloudsql_instance_public_ip" + ], + "excluded_services": ["kms"] + }, + "inserted_at": "2024-07-17T09:50:18.987425Z", + "updated_at": "2024-07-17T09:55:18.987425Z", + "created_by": { + "object": "user", + "id": "eea048ab-7cb3-47eb-9e5e-dce591ade41f" + } + } + } + ], + "meta": { + "pagination": { + "page": 1, + "pages": 1, + "count": 3 + }, + "version": "v1" + } +} diff --git a/hooks/index.ts b/hooks/index.ts new file mode 100644 index 0000000000..709aadd864 --- /dev/null +++ b/hooks/index.ts @@ -0,0 +1 @@ +export * from "./useLocalStorage"; diff --git a/hooks/useLocalStorage.ts b/hooks/useLocalStorage.ts new file mode 100644 index 0000000000..405064fb57 --- /dev/null +++ b/hooks/useLocalStorage.ts @@ -0,0 +1,46 @@ +"use client"; + +import { useEffect, useState } from "react"; + +export const useLocalStorage = ( + key: string, + initialValue: string | boolean, +): [ + string | boolean, + ( + value: string | boolean | ((val: string | boolean) => string | boolean), + ) => void, +] => { + const [state, setState] = useState(initialValue); + + useEffect(() => { + try { + const value = window.localStorage.getItem(key); + + if (value) { + setState(JSON.parse(value)); + } + } catch (error) { + console.log(error); + } + }, [key]); + + const setValue = ( + value: string | boolean | ((val: string | boolean) => string | boolean), + ) => { + try { + // If the passed value is a callback function, + // then call it with the existing state. + const valueToStore = + typeof value === "function" + ? (value as (val: string | boolean) => string | boolean)(state) + : value; + window.localStorage.setItem(key, JSON.stringify(valueToStore)); + setState(valueToStore); + } catch (error) { + console.log(error); + } + }; + + return [state, setValue]; +}; diff --git a/lib/actions/index.ts b/lib/actions/index.ts new file mode 100644 index 0000000000..67b97f1e4a --- /dev/null +++ b/lib/actions/index.ts @@ -0,0 +1 @@ +export * from "./provider.actions"; diff --git a/lib/actions/provider.actions.ts b/lib/actions/provider.actions.ts new file mode 100644 index 0000000000..12ee153dc1 --- /dev/null +++ b/lib/actions/provider.actions.ts @@ -0,0 +1,14 @@ +import "server-only"; + +import { parseStringify } from "../utils"; + +export const getProvider = async () => { + const key = process.env.LOCAL_SITE_URL; + try { + const providers = await fetch(`${key}/api/providers`); + const data = await providers.json(); + return parseStringify(data); + } catch (error) { + console.log(error); + } +}; diff --git a/lib/index.ts b/lib/index.ts new file mode 100644 index 0000000000..178cd64f81 --- /dev/null +++ b/lib/index.ts @@ -0,0 +1 @@ +export * from "./utils"; diff --git a/lib/utils.ts b/lib/utils.ts new file mode 100644 index 0000000000..261f2865a8 --- /dev/null +++ b/lib/utils.ts @@ -0,0 +1,81 @@ +import { type ClassValue, clsx } from "clsx"; +import { twMerge } from "tailwind-merge"; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} + +export function capitalize(str: string): string { + return str.charAt(0).toUpperCase() + str.slice(1); +} + +export const parseStringify = (value: any) => JSON.parse(JSON.stringify(value)); + +export const convertFileToUrl = (file: File) => URL.createObjectURL(file); + +// FORMAT DATE TIME +export const formatDateTime = (dateString: Date | string) => { + const dateTimeOptions: Intl.DateTimeFormatOptions = { + // weekday: "short", // abbreviated weekday name (e.g., 'Mon') + month: "short", // abbreviated month name (e.g., 'Oct') + day: "numeric", // numeric day of the month (e.g., '25') + year: "numeric", // numeric year (e.g., '2023') + hour: "numeric", // numeric hour (e.g., '8') + minute: "numeric", // numeric minute (e.g., '30') + hour12: true, // use 12-hour clock (true) or 24-hour clock (false) + }; + + const dateDayOptions: Intl.DateTimeFormatOptions = { + weekday: "short", // abbreviated weekday name (e.g., 'Mon') + year: "numeric", // numeric year (e.g., '2023') + month: "2-digit", // abbreviated month name (e.g., 'Oct') + day: "2-digit", // numeric day of the month (e.g., '25') + }; + + const dateOptions: Intl.DateTimeFormatOptions = { + month: "short", // abbreviated month name (e.g., 'Oct') + year: "numeric", // numeric year (e.g., '2023') + day: "numeric", // numeric day of the month (e.g., '25') + }; + + const timeOptions: Intl.DateTimeFormatOptions = { + hour: "numeric", // numeric hour (e.g., '8') + minute: "numeric", // numeric minute (e.g., '30') + hour12: true, // use 12-hour clock (true) or 24-hour clock (false) + }; + + const formattedDateTime: string = new Date(dateString).toLocaleString( + "en-US", + dateTimeOptions, + ); + + const formattedDateDay: string = new Date(dateString).toLocaleString( + "en-US", + dateDayOptions, + ); + + const formattedDate: string = new Date(dateString).toLocaleString( + "en-US", + dateOptions, + ); + + const formattedTime: string = new Date(dateString).toLocaleString( + "en-US", + timeOptions, + ); + + return { + dateTime: formattedDateTime, + dateDay: formattedDateDay, + dateOnly: formattedDate, + timeOnly: formattedTime, + }; +}; + +export function encryptKey(passkey: string) { + return btoa(passkey); +} + +export function decryptKey(passkey: string) { + return atob(passkey); +} diff --git a/package.json b/package.json index 364eefd425..c0dd46c781 100644 --- a/package.json +++ b/package.json @@ -5,15 +5,21 @@ "@nextui-org/theme": "2.2.5", "@react-aria/ssr": "3.9.4", "@react-aria/visually-hidden": "3.8.12", - "clsx": "2.1.1", + "@tanstack/react-table": "^8.19.3", + "class-variance-authority": "^0.7.0", + "clsx": "^2.1.1", "date-fns": "^3.6.0", "framer-motion": "~11.1.1", "intl-messageformat": "^10.5.0", + "lucide-react": "^0.417.0", "next": "14.2.3", "next-themes": "^0.2.1", "react": "18.3.1", "react-dom": "18.3.1", - "swr": "^2.2.5" + "server-only": "^0.0.1", + "swr": "^2.2.5", + "tailwind-merge": "^2.4.0", + "tailwindcss-animate": "^1.0.7" }, "devDependencies": { "@iconify/react": "^5.0.1", diff --git a/types/components.ts b/types/components.ts index c9b078e314..994adf02a2 100644 --- a/types/components.ts +++ b/types/components.ts @@ -8,3 +8,30 @@ export type IconProps = { icon: React.FC; style?: React.CSSProperties; }; + +export interface ProviderProps { + id: string; + type: "providers"; + attributes: { + provider: "aws" | "azure" | "gcp"; + provider_id: string; + alias: string; + status: "completed" | "pending" | "cancelled"; + resources: number; + connection: { + connected: boolean; + last_checked_at: string; + }; + scanner_args: { + only_logs: boolean; + excluded_checks: string[]; + aws_retries_max_attempts: number; + }; + inserted_at: string; + updated_at: string; + created_by: { + object: string; + id: string; + }; + }; +} diff --git a/utils/capitalize.ts b/utils/capitalize.ts deleted file mode 100644 index 9996d36f81..0000000000 --- a/utils/capitalize.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function capitalize(str: string): string { - return str.charAt(0).toUpperCase() + str.slice(1); -} diff --git a/utils/fetcher.tsx b/utils/fetcher.tsx deleted file mode 100644 index 50b047c9ad..0000000000 --- a/utils/fetcher.tsx +++ /dev/null @@ -1 +0,0 @@ -export const fetcher = (url: string) => fetch(url).then((res) => res.json()); From 9d66a7ec4a11dc5447975b373b6a0a78194055e6 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Tue, 30 Jul 2024 12:54:52 +0200 Subject: [PATCH 071/411] feat: handle error when the endpoint is not working --- app/(prowler)/providers/page.tsx | 5 ++++- lib/actions/provider.actions.ts | 7 ++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/app/(prowler)/providers/page.tsx b/app/(prowler)/providers/page.tsx index 2301d26738..d51901ef7b 100644 --- a/app/(prowler)/providers/page.tsx +++ b/app/(prowler)/providers/page.tsx @@ -28,7 +28,10 @@ export default async function Providers() { openButtonLabel="Add Cloud Accounts" /> - + ); diff --git a/lib/actions/provider.actions.ts b/lib/actions/provider.actions.ts index 12ee153dc1..c1973f0917 100644 --- a/lib/actions/provider.actions.ts +++ b/lib/actions/provider.actions.ts @@ -4,11 +4,16 @@ import { parseStringify } from "../utils"; export const getProvider = async () => { const key = process.env.LOCAL_SITE_URL; + + if (!key) return undefined; + try { const providers = await fetch(`${key}/api/providers`); + const data = await providers.json(); + return parseStringify(data); } catch (error) { - console.log(error); + return undefined; } }; From 3fa614341ff93ca51ac33c4c4cbf47ea73ab6ad7 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Tue, 30 Jul 2024 17:25:58 +0200 Subject: [PATCH 072/411] fix: fix typo error --- lib/actions/provider.actions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/actions/provider.actions.ts b/lib/actions/provider.actions.ts index 2b2a0dab82..c1973f0917 100644 --- a/lib/actions/provider.actions.ts +++ b/lib/actions/provider.actions.ts @@ -8,7 +8,7 @@ export const getProvider = async () => { if (!key) return undefined; try { - const providers = await fetch(`${key}/api/proiders`); + const providers = await fetch(`${key}/api/providers`); const data = await providers.json(); From 0a0a08b97d64bf237ad8975784eb59d651d1c7ef Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Wed, 31 Jul 2024 08:50:31 +0200 Subject: [PATCH 073/411] chore: remove SWR library from the project and add alert from shadcn --- package.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index c0dd46c781..65480a62fd 100644 --- a/package.json +++ b/package.json @@ -3,9 +3,12 @@ "@nextui-org/react": "^2.4.2", "@nextui-org/system": "2.2.1", "@nextui-org/theme": "2.2.5", + "@radix-ui/react-icons": "^1.3.0", "@react-aria/ssr": "3.9.4", "@react-aria/visually-hidden": "3.8.12", "@tanstack/react-table": "^8.19.3", + "add": "^2.0.6", + "alert": "^6.0.2", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "date-fns": "^3.6.0", @@ -17,7 +20,7 @@ "react": "18.3.1", "react-dom": "18.3.1", "server-only": "^0.0.1", - "swr": "^2.2.5", + "shadcn-ui": "^0.8.0", "tailwind-merge": "^2.4.0", "tailwindcss-animate": "^1.0.7" }, From ddf9a3ef2d89386d255b7f16bfe5ecd60c5297b6 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Wed, 31 Jul 2024 08:55:02 +0200 Subject: [PATCH 074/411] feat: implement error boundary functionality --- app/(prowler)/error.tsx | 36 +++++++++++++++++++ app/(prowler)/layout.tsx | 2 +- app/(prowler)/providers/page.tsx | 2 +- app/error.tsx | 31 ----------------- components/icons/Icons.tsx | 22 ++++++++++++ components/index.ts | 1 + components/ui/alert/Alert.tsx | 59 ++++++++++++++++++++++++++++++++ lib/utils.ts | 15 ++++---- tailwind.config.js | 31 +++++++++++++---- 9 files changed, 154 insertions(+), 45 deletions(-) create mode 100644 app/(prowler)/error.tsx delete mode 100644 app/error.tsx create mode 100644 components/ui/alert/Alert.tsx diff --git a/app/(prowler)/error.tsx b/app/(prowler)/error.tsx new file mode 100644 index 0000000000..fd8468f123 --- /dev/null +++ b/app/(prowler)/error.tsx @@ -0,0 +1,36 @@ +"use client"; + +import Link from "next/link"; +import { useEffect } from "react"; + +import { Alert, AlertDescription, AlertTitle } from "@/components"; +import { RocketIcon } from "@/components/icons"; + +export default function Error({ + error, + // reset, +}: { + error: Error; + reset: () => void; +}) { + useEffect(() => { + // Log the error to an error reporting service + /* eslint-disable no-console */ + console.error(error); + }, [error]); + + return ( + + + An unexpected error occurred + + We're sorry for the inconvenience. Please try again or contact support + if the problem persists. + + + {" "} + Go to the homepage + + + ); +} diff --git a/app/(prowler)/layout.tsx b/app/(prowler)/layout.tsx index 13319c3540..6ef0e1aaa4 100644 --- a/app/(prowler)/layout.tsx +++ b/app/(prowler)/layout.tsx @@ -3,7 +3,7 @@ import "@/styles/globals.css"; import { Metadata, Viewport } from "next"; import React from "react"; -import { SidebarWrap } from "@/components"; +import { SidebarWrap } from "@/components/ui/sidebar"; import { fontSans } from "@/config/fonts"; import { siteConfig } from "@/config/site"; import { cn } from "@/lib/utils"; diff --git a/app/(prowler)/providers/page.tsx b/app/(prowler)/providers/page.tsx index d51901ef7b..4301b83e37 100644 --- a/app/(prowler)/providers/page.tsx +++ b/app/(prowler)/providers/page.tsx @@ -30,7 +30,7 @@ export default async function Providers() { diff --git a/app/error.tsx b/app/error.tsx deleted file mode 100644 index 9ed5104e8a..0000000000 --- a/app/error.tsx +++ /dev/null @@ -1,31 +0,0 @@ -"use client"; - -import { useEffect } from "react"; - -export default function Error({ - error, - reset, -}: { - error: Error; - reset: () => void; -}) { - useEffect(() => { - // Log the error to an error reporting service - /* eslint-disable no-console */ - console.error(error); - }, [error]); - - return ( -
-

Something went wrong!

- -
- ); -} diff --git a/components/icons/Icons.tsx b/components/icons/Icons.tsx index 9b79a7c35d..aca8d6fc4d 100644 --- a/components/icons/Icons.tsx +++ b/components/icons/Icons.tsx @@ -270,3 +270,25 @@ export const WifiOffIcon: React.FC = ({ /> ); + +export const RocketIcon: React.FC = ({ + size = 24, + width, + height, + ...props +}) => { + return ( + + + + ); +}; diff --git a/components/index.ts b/components/index.ts index faa5b19c57..cc6a14b022 100644 --- a/components/index.ts +++ b/components/index.ts @@ -3,6 +3,7 @@ export * from "./providers/ProviderInfo"; export * from "./providers/ScanStatus"; export * from "./providers/table/ColumnsProviders"; export * from "./providers/table/DataTable"; +export * from "./ui/alert/Alert"; export * from "./ui/header/Header"; export * from "./ui/modal"; export * from "./ui/sidebar"; diff --git a/components/ui/alert/Alert.tsx b/components/ui/alert/Alert.tsx new file mode 100644 index 0000000000..7f6fb50705 --- /dev/null +++ b/components/ui/alert/Alert.tsx @@ -0,0 +1,59 @@ +import { cva, type VariantProps } from "class-variance-authority"; +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +const alertVariants = cva( + "relative w-full rounded-lg border border-slate-200 px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-slate-950 [&>svg~*]:pl-7 dark:border-slate-800 dark:[&>svg]:text-slate-50", + { + variants: { + variant: { + default: "bg-white text-slate-950 dark:bg-slate-950 dark:text-slate-50", + destructive: + "border-red-500/50 text-red-500 dark:border-red-500 [&>svg]:text-red-500 dark:border-red-900/50 dark:text-red-900 dark:dark:border-red-900 dark:[&>svg]:text-red-900", + }, + }, + defaultVariants: { + variant: "default", + }, + }, +); + +const Alert = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes & VariantProps +>(({ className, variant, ...props }, ref) => ( +
+)); +Alert.displayName = "Alert"; + +const AlertTitle = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +AlertTitle.displayName = "AlertTitle"; + +const AlertDescription = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +AlertDescription.displayName = "AlertDescription"; + +export { Alert, AlertDescription, AlertTitle }; diff --git a/lib/utils.ts b/lib/utils.ts index 261f2865a8..c3e96dd8df 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -5,16 +5,15 @@ export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); } -export function capitalize(str: string): string { - return str.charAt(0).toUpperCase() + str.slice(1); -} - export const parseStringify = (value: any) => JSON.parse(JSON.stringify(value)); export const convertFileToUrl = (file: File) => URL.createObjectURL(file); // FORMAT DATE TIME -export const formatDateTime = (dateString: Date | string) => { +export const formatDateTime = ( + dateString: Date | string, + timeZone: string = Intl.DateTimeFormat().resolvedOptions().timeZone, +) => { const dateTimeOptions: Intl.DateTimeFormatOptions = { // weekday: "short", // abbreviated weekday name (e.g., 'Mon') month: "short", // abbreviated month name (e.g., 'Oct') @@ -22,7 +21,8 @@ export const formatDateTime = (dateString: Date | string) => { year: "numeric", // numeric year (e.g., '2023') hour: "numeric", // numeric hour (e.g., '8') minute: "numeric", // numeric minute (e.g., '30') - hour12: true, // use 12-hour clock (true) or 24-hour clock (false) + hour12: true, // use 12-hour clock (true) or 24-hour clock (false), + timeZone: timeZone, // use the provided timezone }; const dateDayOptions: Intl.DateTimeFormatOptions = { @@ -30,18 +30,21 @@ export const formatDateTime = (dateString: Date | string) => { year: "numeric", // numeric year (e.g., '2023') month: "2-digit", // abbreviated month name (e.g., 'Oct') day: "2-digit", // numeric day of the month (e.g., '25') + timeZone: timeZone, // use the provided timezone }; const dateOptions: Intl.DateTimeFormatOptions = { month: "short", // abbreviated month name (e.g., 'Oct') year: "numeric", // numeric year (e.g., '2023') day: "numeric", // numeric day of the month (e.g., '25') + timeZone: timeZone, // use the provided timezone }; const timeOptions: Intl.DateTimeFormatOptions = { hour: "numeric", // numeric hour (e.g., '8') minute: "numeric", // numeric minute (e.g., '30') hour12: true, // use 12-hour clock (true) or 24-hour clock (false) + timeZone: timeZone, // use the provided timezone }; const formattedDateTime: string = new Date(dateString).toLocaleString( diff --git a/tailwind.config.js b/tailwind.config.js index 5a6f937328..ac42eb57ec 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -2,22 +2,41 @@ import { nextui } from "@nextui-org/theme"; /** @type {import('tailwindcss').Config} */ module.exports = { + darkMode: ["class"], content: [ - "./components/**/*.{js,ts,jsx,tsx,mdx}", - "./app/**/*.{js,ts,jsx,tsx,mdx}", + "./components/**/*.{ts,jsx,tsx,mdx}", + "./app/**/*.{ts,jsx,tsx,mdx}", "./node_modules/@nextui-org/theme/dist/**/*.{js,ts,jsx,tsx}", ], + prefix: "", theme: { + container: { + center: true, + padding: "2rem", + screens: { + "2xl": "1400px", + }, + }, extend: { fontFamily: { sans: ["var(--font-sans)"], mono: ["var(--font-geist-mono)"], }, - height: { - "dvh-minus-16": "calc(100dvh - 116px)", + keyframes: { + "accordion-down": { + from: { height: "0" }, + to: { height: "var(--radix-accordion-content-height)" }, + }, + "accordion-up": { + from: { height: "var(--radix-accordion-content-height)" }, + to: { height: "0" }, + }, + }, + animation: { + "accordion-down": "accordion-down 0.2s ease-out", + "accordion-up": "accordion-up 0.2s ease-out", }, }, }, - darkMode: "class", - plugins: [nextui()], + plugins: [require("tailwindcss-animate"), nextui()], }; From 89d15c40da5a8e57ffb0ec3529c2bcf898c5c1d6 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Wed, 31 Jul 2024 21:52:59 +0200 Subject: [PATCH 075/411] feat: update TypeScript to the latest version --- app/(prowler)/providers/page.tsx | 29 +++++++-- components/index.ts | 1 + .../providers/table/SkeletonTableProvider.tsx | 65 +++++++++++++++++++ package.json | 2 +- 4 files changed, 89 insertions(+), 8 deletions(-) create mode 100644 components/providers/table/SkeletonTableProvider.tsx diff --git a/app/(prowler)/providers/page.tsx b/app/(prowler)/providers/page.tsx index 4301b83e37..df2b7c01e8 100644 --- a/app/(prowler)/providers/page.tsx +++ b/app/(prowler)/providers/page.tsx @@ -1,11 +1,16 @@ import { Spacer } from "@nextui-org/react"; -import React from "react"; +import React, { Suspense } from "react"; -import { ColumnsProviders, DataTable, Header, ModalWrap } from "@/components"; +import { + ColumnsProviders, + DataTable, + Header, + ModalWrap, + SkeletonTableProvider, +} from "@/components"; import { getProvider } from "@/lib/actions"; export default async function Providers() { - const providers = await getProvider(); const onSave = async () => { "use server"; // event we want to pass down, ex. console.log("### hello"); @@ -28,11 +33,21 @@ export default async function Providers() { openButtonLabel="Add Cloud Accounts" /> - + }> + +
); } + +const SSRDataTable = async () => { + const providersData = await getProvider(); + const [providers] = await Promise.all([providersData]); + return ( + + ); +}; diff --git a/components/index.ts b/components/index.ts index cc6a14b022..4cad0baacf 100644 --- a/components/index.ts +++ b/components/index.ts @@ -3,6 +3,7 @@ export * from "./providers/ProviderInfo"; export * from "./providers/ScanStatus"; export * from "./providers/table/ColumnsProviders"; export * from "./providers/table/DataTable"; +export * from "./providers/table/SkeletonTableProvider"; export * from "./ui/alert/Alert"; export * from "./ui/header/Header"; export * from "./ui/modal"; diff --git a/components/providers/table/SkeletonTableProvider.tsx b/components/providers/table/SkeletonTableProvider.tsx new file mode 100644 index 0000000000..e43ca38cab --- /dev/null +++ b/components/providers/table/SkeletonTableProvider.tsx @@ -0,0 +1,65 @@ +import { Card, Skeleton } from "@nextui-org/react"; +import React from "react"; + +export const SkeletonTableProvider = () => { + return ( + + {/* Table headers */} +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+ + {/* Table body */} +
+ {[...Array(3)].map((_, index) => ( +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+ ))} +
+
+ ); +}; diff --git a/package.json b/package.json index 65480a62fd..9178826cb9 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "postcss": "8.4.38", "tailwind-variants": "0.1.20", "tailwindcss": "3.4.3", - "typescript": "5.0.4", + "typescript": "^5.5.4", "usehooks-ts": "^3.1.0" }, "name": "prowler-next-app", From 92e88674f6869f8f7b1d696f6c4789f92fe52c1e Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Wed, 31 Jul 2024 21:56:05 +0200 Subject: [PATCH 076/411] feat: update TypeScript to the latest version and remove library not used for icons --- package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index 65480a62fd..caad2dcdb8 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,6 @@ "@nextui-org/react": "^2.4.2", "@nextui-org/system": "2.2.1", "@nextui-org/theme": "2.2.5", - "@radix-ui/react-icons": "^1.3.0", "@react-aria/ssr": "3.9.4", "@react-aria/visually-hidden": "3.8.12", "@tanstack/react-table": "^8.19.3", @@ -49,7 +48,7 @@ "postcss": "8.4.38", "tailwind-variants": "0.1.20", "tailwindcss": "3.4.3", - "typescript": "5.0.4", + "typescript": "^5.5.4", "usehooks-ts": "^3.1.0" }, "name": "prowler-next-app", From d431516270dbaf3fedd0b09e11b8fed94714f107 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Wed, 31 Jul 2024 21:56:50 +0200 Subject: [PATCH 077/411] chore:remove library not used for icons --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 9178826cb9..caad2dcdb8 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,6 @@ "@nextui-org/react": "^2.4.2", "@nextui-org/system": "2.2.1", "@nextui-org/theme": "2.2.5", - "@radix-ui/react-icons": "^1.3.0", "@react-aria/ssr": "3.9.4", "@react-aria/visually-hidden": "3.8.12", "@tanstack/react-table": "^8.19.3", From e5a328e9eacf1b994c9f7f429930773db58e88d7 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Thu, 1 Aug 2024 12:09:37 +0200 Subject: [PATCH 078/411] feat: change configuration and generate package-lock file --- .npmrc | 2 +- next.config.js | 6 +- package-lock.json | 10602 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 10606 insertions(+), 4 deletions(-) create mode 100644 package-lock.json diff --git a/.npmrc b/.npmrc index 43c97e719a..cafe685a11 100644 --- a/.npmrc +++ b/.npmrc @@ -1 +1 @@ -package-lock=false +package-lock=true diff --git a/next.config.js b/next.config.js index 658404ac69..b9f289635e 100644 --- a/next.config.js +++ b/next.config.js @@ -1,4 +1,4 @@ /** @type {import('next').NextConfig} */ -const nextConfig = {}; - -module.exports = nextConfig; +module.exports = { + output: "standalone", +}; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000000..860d71df9d --- /dev/null +++ b/package-lock.json @@ -0,0 +1,10602 @@ +{ + "name": "prowler-next-app", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "prowler-next-app", + "version": "0.0.1", + "dependencies": { + "@nextui-org/react": "^2.4.2", + "@nextui-org/system": "2.2.1", + "@nextui-org/theme": "2.2.5", + "@react-aria/ssr": "3.9.4", + "@react-aria/visually-hidden": "3.8.12", + "@tanstack/react-table": "^8.19.3", + "add": "^2.0.6", + "alert": "^6.0.2", + "class-variance-authority": "^0.7.0", + "clsx": "^2.1.1", + "date-fns": "^3.6.0", + "framer-motion": "~11.1.1", + "intl-messageformat": "^10.5.0", + "lucide-react": "^0.417.0", + "next": "14.2.3", + "next-themes": "^0.2.1", + "react": "18.3.1", + "react-dom": "18.3.1", + "server-only": "^0.0.1", + "shadcn-ui": "^0.8.0", + "tailwind-merge": "^2.4.0", + "tailwindcss-animate": "^1.0.7" + }, + "devDependencies": { + "@iconify/react": "^5.0.1", + "@types/node": "20.5.7", + "@types/react": "18.3.3", + "@types/react-dom": "18.3.0", + "@typescript-eslint/eslint-plugin": "^7.10.0", + "@typescript-eslint/parser": "^7.10.0", + "autoprefixer": "10.4.19", + "eslint": "^8.56.0", + "eslint-config-next": "14.2.1", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-import": "^2.26.0", + "eslint-plugin-jsx-a11y": "^6.4.1", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-prettier": "^5.1.3", + "eslint-plugin-react": "^7.23.2", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-security": "^3.0.1", + "eslint-plugin-simple-import-sort": "^12.1.1", + "eslint-plugin-unused-imports": "^3.2.0", + "husky": "^9.0.11", + "lint-staged": "^15.2.7", + "postcss": "8.4.38", + "tailwind-variants": "0.1.20", + "tailwindcss": "3.4.3", + "typescript": "^5.5.4", + "usehooks-ts": "^3.1.0" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@antfu/ni": { + "version": "0.21.12", + "resolved": "https://registry.npmjs.org/@antfu/ni/-/ni-0.21.12.tgz", + "integrity": "sha512-2aDL3WUv8hMJb2L3r/PIQWsTLyq7RQr3v9xD16fiz6O8ys1xEyLhhTOv8gxtZvJiTzjTF5pHoArvRdesGL1DMQ==", + "bin": { + "na": "bin/na.mjs", + "nci": "bin/nci.mjs", + "ni": "bin/ni.mjs", + "nlx": "bin/nlx.mjs", + "nr": "bin/nr.mjs", + "nu": "bin/nu.mjs", + "nun": "bin/nun.mjs" + } + }, + "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==", + "dependencies": { + "@babel/highlight": "^7.24.7", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.2.tgz", + "integrity": "sha512-bYcppcpKBvX4znYaPEeFau03bp89ShqNMLs+rmdptMw+heSZh9+z84d2YG+K7cYLbWwzdjtDoW/uqZmPjulClQ==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz", + "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.25.0", + "@babel/helper-compilation-targets": "^7.25.2", + "@babel/helper-module-transforms": "^7.25.2", + "@babel/helpers": "^7.25.0", + "@babel/parser": "^7.25.0", + "@babel/template": "^7.25.0", + "@babel/traverse": "^7.25.2", + "@babel/types": "^7.25.2", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.0.tgz", + "integrity": "sha512-3LEEcj3PVW8pW2R1SR1M89g/qrYk/m/mB/tLqn7dn4sbBUQyTqnlod+II2U4dqiGtUmkcnAmkMDralTFZttRiw==", + "dependencies": { + "@babel/types": "^7.25.0", + "@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-annotate-as-pure": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz", + "integrity": "sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg==", + "dependencies": { + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz", + "integrity": "sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==", + "dependencies": { + "@babel/compat-data": "^7.25.2", + "@babel/helper-validator-option": "^7.24.8", + "browserslist": "^4.23.1", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.0.tgz", + "integrity": "sha512-GYM6BxeQsETc9mnct+nIIpf63SAyzvyYN7UB/IlTyd+MBg06afFGp0mIeUqGyWgS2mxad6vqbMrHVlaL3m70sQ==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-member-expression-to-functions": "^7.24.8", + "@babel/helper-optimise-call-expression": "^7.24.7", + "@babel/helper-replace-supers": "^7.25.0", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", + "@babel/traverse": "^7.25.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.8.tgz", + "integrity": "sha512-LABppdt+Lp/RlBxqrh4qgf1oEH/WxdzQNDJIu5gC/W1GyvPVrOBiItmmM8wan2fm4oYqFuFfkXmlGpLQhPY8CA==", + "dependencies": { + "@babel/traverse": "^7.24.8", + "@babel/types": "^7.24.8" + }, + "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==", + "dependencies": { + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz", + "integrity": "sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==", + "dependencies": { + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-simple-access": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7", + "@babel/traverse": "^7.25.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.24.7.tgz", + "integrity": "sha512-jKiTsW2xmWwxT1ixIdfXUZp+P5yURx2suzLZr5Hi64rURpDYdMW0pv+Uf17EYk2Rd428Lx4tLsnjGJzYKDM/6A==", + "dependencies": { + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz", + "integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.25.0.tgz", + "integrity": "sha512-q688zIvQVYtZu+i2PsdIu/uWGRpfxzr5WESsfpShfZECkO+d2o+WROWezCi/Q6kJ0tfPa5+pUGUlfx2HhrA3Bg==", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.24.8", + "@babel/helper-optimise-call-expression": "^7.24.7", + "@babel/traverse": "^7.25.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", + "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", + "dependencies": { + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.24.7.tgz", + "integrity": "sha512-IO+DLT3LQUElMbpzlatRASEyQtfhSE0+m465v++3jyyXeBTBUjtVZg28/gHeV5mrTJqvEKhKroBGAvhW+qPHiQ==", + "dependencies": { + "@babel/traverse": "^7.24.7", + "@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==", + "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==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz", + "integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.0.tgz", + "integrity": "sha512-MjgLZ42aCm0oGjJj8CtSM3DB8NOOf8h2l7DCTePJs29u+v7yO/RBX9nShlKMgFnRks/Q4tBAe7Hxnov9VkGwLw==", + "dependencies": { + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.0" + }, + "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==", + "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==", + "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==", + "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==", + "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==" + }, + "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==", + "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==", + "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==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/parser": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.0.tgz", + "integrity": "sha512-CzdIU9jdP0dg7HdyB+bHvDJGagUv+qtzZt5rYCWwW6tITNqV9odjp6Qu41gkG0ca5UfdDUWrKkiAnHHdGRnOrA==", + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.7.tgz", + "integrity": "sha512-c/+fVeJBB0FeKsFvwytYiUD+LBvhHjGSI0g446PRGdSVGZLRNArBUno2PETbAly3tpiNAQR5XaZ+JslxkotsbA==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.25.2.tgz", + "integrity": "sha512-lBwRvjSmqiMYe/pS0+1gggjJleUJi7NzjvQ1Fkqtt69hBa/0t1YuW/MLQMAPixfwaQOHUXsd6jeU3Z+vdGv3+A==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-create-class-features-plugin": "^7.25.0", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", + "@babel/plugin-syntax-typescript": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-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/@babel/template": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz", + "integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==", + "dependencies": { + "@babel/code-frame": "^7.24.7", + "@babel/parser": "^7.25.0", + "@babel/types": "^7.25.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.2.tgz", + "integrity": "sha512-s4/r+a7xTnny2O6FcZzqgT6nE4/GHEdcqj4qAeglbUOh0TeglEfmNJFAd/OLoVtGd6ZhAO8GCVvCNUO5t/VJVQ==", + "dependencies": { + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.25.0", + "@babel/parser": "^7.25.0", + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.2", + "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==", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/types": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.2.tgz", + "integrity": "sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==", + "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/@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.11.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz", + "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==", + "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/@formatjs/ecma402-abstract": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.0.0.tgz", + "integrity": "sha512-rRqXOqdFmk7RYvj4khklyqzcfQl9vEL/usogncBHRZfZBDOwMGuSRNFl02fu5KGHXdbinju+YXyuR+Nk8xlr/g==", + "dependencies": { + "@formatjs/intl-localematcher": "0.5.4", + "tslib": "^2.4.0" + } + }, + "node_modules/@formatjs/fast-memoize": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.0.tgz", + "integrity": "sha512-hnk/nY8FyrL5YxwP9e4r9dqeM6cAbo8PeU9UjyXojZMNvVad2Z06FAVHyR3Ecw6fza+0GH7vdJgiKIVXTMbSBA==", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@formatjs/icu-messageformat-parser": { + "version": "2.7.8", + "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.7.8.tgz", + "integrity": "sha512-nBZJYmhpcSX0WeJ5SDYUkZ42AgR3xiyhNCsQweFx3cz/ULJjym8bHAzWKvG5e2+1XO98dBYC0fWeeAECAVSwLA==", + "dependencies": { + "@formatjs/ecma402-abstract": "2.0.0", + "@formatjs/icu-skeleton-parser": "1.8.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@formatjs/icu-skeleton-parser": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.2.tgz", + "integrity": "sha512-k4ERKgw7aKGWJZgTarIcNEmvyTVD9FYh0mTrrBMHZ1b8hUu6iOJ4SzsZlo3UNAvHYa+PnvntIwRPt1/vy4nA9Q==", + "dependencies": { + "@formatjs/ecma402-abstract": "2.0.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@formatjs/intl-localematcher": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.4.tgz", + "integrity": "sha512-zTwEpWOzZ2CiKcB93BLngUX59hQkuZjT2+SAQEscSm52peDW/getsawMcWF1rGRpMCX6D7nSJA3CzJ8gn13N/g==", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "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==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true + }, + "node_modules/@iconify/react": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@iconify/react/-/react-5.0.1.tgz", + "integrity": "sha512-octpAJRtHZLLS1o6fmz2Ek2Rfwx75kVg48MZyGTqL3QqoxRddEsuLqOt6ADDhRosmlrYnIrVL+7obo1bz2ikNw==", + "dev": true, + "dependencies": { + "@iconify/types": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/cyberalien" + }, + "peerDependencies": { + "react": ">=16" + } + }, + "node_modules/@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", + "dev": true + }, + "node_modules/@internationalized/date": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.5.4.tgz", + "integrity": "sha512-qoVJVro+O0rBaw+8HPjUB1iH8Ihf8oziEnqMnvhJUSuVIrHOuZ6eNLHNvzXJKUvAtaDiqMnRlg8Z2mgh09BlUw==", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@internationalized/message": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@internationalized/message/-/message-3.1.4.tgz", + "integrity": "sha512-Dygi9hH1s7V9nha07pggCkvmRfDd3q2lWnMGvrJyrOwYMe1yj4D2T9BoH9I6MGR7xz0biQrtLPsqUkqXzIrBOw==", + "dependencies": { + "@swc/helpers": "^0.5.0", + "intl-messageformat": "^10.1.0" + } + }, + "node_modules/@internationalized/number": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/@internationalized/number/-/number-3.5.3.tgz", + "integrity": "sha512-rd1wA3ebzlp0Mehj5YTuTI50AQEx80gWFyHcQu+u91/5NgdwBecO8BH6ipPfE+lmQ9d63vpB3H9SHoIUiupllw==", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@internationalized/string": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/@internationalized/string/-/string-3.2.3.tgz", + "integrity": "sha512-9kpfLoA8HegiWTeCbR2livhdVeKobCnVv8tlJ6M2jF+4tcMqDo94ezwlnrUANBWPgd8U7OXIHCk2Ov2qhk4KXw==", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "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==", + "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==", + "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==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + }, + "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==", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@next/env": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.3.tgz", + "integrity": "sha512-W7fd7IbkfmeeY2gXrzJYDx8D2lWKbVoTIj1o1ScPHNzvp30s1AuoEFSdr39bC5sjxJaxTtq3OTCZboNp0lNWHA==" + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.3.tgz", + "integrity": "sha512-3pEYo/RaGqPP0YzwnlmPN2puaF2WMLM3apt5jLW2fFdXD9+pqcoTzRk+iZsf8ta7+quAe4Q6Ms0nR0SFGFdS1A==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nextui-org/accordion": { + "version": "2.0.35", + "resolved": "https://registry.npmjs.org/@nextui-org/accordion/-/accordion-2.0.35.tgz", + "integrity": "sha512-42T8DAgpICKORry5h1UCgAQ71QJ3dCzvqrnnJQco3LICeIER2JT/wEdpxHUVT893MkL6z6CFsJmWNfFJPk59kA==", + "dependencies": { + "@nextui-org/aria-utils": "2.0.21", + "@nextui-org/divider": "2.0.28", + "@nextui-org/framer-utils": "2.0.21", + "@nextui-org/react-utils": "2.0.14", + "@nextui-org/shared-icons": "2.0.8", + "@nextui-org/shared-utils": "2.0.5", + "@nextui-org/use-aria-accordion": "2.0.6", + "@react-aria/button": "3.9.5", + "@react-aria/focus": "3.17.1", + "@react-aria/interactions": "3.21.3", + "@react-aria/utils": "3.24.1", + "@react-stately/tree": "3.8.1", + "@react-types/accordion": "3.0.0-alpha.21", + "@react-types/shared": "3.23.1" + }, + "peerDependencies": { + "@nextui-org/system": ">=2.0.0", + "@nextui-org/theme": ">=2.1.0", + "framer-motion": ">=10.17.0", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@nextui-org/aria-utils": { + "version": "2.0.21", + "resolved": "https://registry.npmjs.org/@nextui-org/aria-utils/-/aria-utils-2.0.21.tgz", + "integrity": "sha512-aQXFVm4qNrXrUAHhRtr363BgRDX+zgN3Vm+7bW1qtMbnMGOqTWApCD48FP59bka5JArd3K+85tFEhkdD+UfKbQ==", + "dependencies": { + "@nextui-org/react-rsc-utils": "2.0.12", + "@nextui-org/shared-utils": "2.0.5", + "@nextui-org/system": "2.2.2", + "@react-aria/utils": "3.24.1", + "@react-stately/collections": "3.10.7", + "@react-stately/overlays": "3.6.7", + "@react-types/overlays": "3.8.7", + "@react-types/shared": "3.23.1" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@nextui-org/aria-utils/node_modules/@nextui-org/system": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@nextui-org/system/-/system-2.2.2.tgz", + "integrity": "sha512-u30lWSIO4Q7DStiK5tJjDgKBQtmODeQZcC6llz973sJ9QlE4GeC1fgu0+/zXL8AZZ8o/iEXhHWXsZIJ26EquUQ==", + "dependencies": { + "@internationalized/date": "^3.5.4", + "@nextui-org/react-utils": "2.0.14", + "@nextui-org/system-rsc": "2.1.2", + "@react-aria/i18n": "3.11.1", + "@react-aria/overlays": "3.22.1", + "@react-aria/utils": "3.24.1", + "@react-stately/utils": "3.10.1" + }, + "peerDependencies": { + "framer-motion": ">=10.17.0", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@nextui-org/autocomplete": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@nextui-org/autocomplete/-/autocomplete-2.1.2.tgz", + "integrity": "sha512-3mtYQDBbSRLG8wZ+gDMsOsGH/0m2VG/RcwIiXoteZMyX7yhGl2JPp7ZjX6XWyUpUbq0w2QVprZ6Ld4ck3cuMKg==", + "dependencies": { + "@nextui-org/aria-utils": "2.0.21", + "@nextui-org/button": "2.0.34", + "@nextui-org/input": "2.2.2", + "@nextui-org/listbox": "2.1.22", + "@nextui-org/popover": "2.1.24", + "@nextui-org/react-utils": "2.0.14", + "@nextui-org/scroll-shadow": "2.1.17", + "@nextui-org/shared-icons": "2.0.8", + "@nextui-org/shared-utils": "2.0.5", + "@nextui-org/spinner": "2.0.30", + "@nextui-org/use-aria-button": "2.0.9", + "@nextui-org/use-safe-layout-effect": "2.0.5", + "@react-aria/combobox": "3.9.1", + "@react-aria/focus": "3.17.1", + "@react-aria/i18n": "3.11.1", + "@react-aria/interactions": "3.21.3", + "@react-aria/utils": "3.24.1", + "@react-aria/visually-hidden": "3.8.12", + "@react-stately/combobox": "3.8.4", + "@react-types/combobox": "3.11.1", + "@react-types/shared": "3.23.1" + }, + "peerDependencies": { + "@nextui-org/system": ">=2.0.0", + "@nextui-org/theme": ">=2.1.0", + "framer-motion": ">=10.17.0", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@nextui-org/avatar": { + "version": "2.0.30", + "resolved": "https://registry.npmjs.org/@nextui-org/avatar/-/avatar-2.0.30.tgz", + "integrity": "sha512-FIrvdJE+dBkmU3YDR1AXTkcks/WXjbnQsojWBMAq+1oXDCcNiGMUvKBzsW0F5m5HVHhn+Edc+CbTzIZUTm78Bw==", + "dependencies": { + "@nextui-org/react-utils": "2.0.14", + "@nextui-org/shared-utils": "2.0.5", + "@nextui-org/use-image": "2.0.5", + "@react-aria/focus": "3.17.1", + "@react-aria/interactions": "3.21.3", + "@react-aria/utils": "3.24.1" + }, + "peerDependencies": { + "@nextui-org/system": ">=2.0.0", + "@nextui-org/theme": ">=2.1.0", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@nextui-org/badge": { + "version": "2.0.29", + "resolved": "https://registry.npmjs.org/@nextui-org/badge/-/badge-2.0.29.tgz", + "integrity": "sha512-kd6BJ1BWkX6UuHttmySUgQBPOBJCrG1+eKwWDd1HL4YuBLayuYoTZuE5Q01HYTbXjFMqzsFX3A+jcJ3RYc0X7w==", + "dependencies": { + "@nextui-org/react-utils": "2.0.14", + "@nextui-org/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@nextui-org/system": ">=2.0.0", + "@nextui-org/theme": ">=2.1.0", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@nextui-org/breadcrumbs": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@nextui-org/breadcrumbs/-/breadcrumbs-2.0.10.tgz", + "integrity": "sha512-TCrOHCH/gNrPwEQyd30mu6Y9x/ojJk3vUWZJSPuVhzG6WdpUFyqen4QCoDTUTvFJBL3TwqNYwOIxooizzFSK7g==", + "dependencies": { + "@nextui-org/react-utils": "2.0.14", + "@nextui-org/shared-icons": "2.0.8", + "@nextui-org/shared-utils": "2.0.5", + "@react-aria/breadcrumbs": "3.5.13", + "@react-aria/focus": "3.17.1", + "@react-aria/utils": "3.24.1", + "@react-types/breadcrumbs": "3.7.5", + "@react-types/shared": "3.23.1" + }, + "peerDependencies": { + "@nextui-org/system": ">=2.0.0", + "@nextui-org/theme": ">=2.1.0", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@nextui-org/button": { + "version": "2.0.34", + "resolved": "https://registry.npmjs.org/@nextui-org/button/-/button-2.0.34.tgz", + "integrity": "sha512-VeFpOs7trX6u6FqeGr0XCpuNqPhXTLqsmt4iaygvheZCbzrTKvWHd4QMqSh2CPsNH8UFUBSFJjr3oaf3a0SYWQ==", + "dependencies": { + "@nextui-org/react-utils": "2.0.14", + "@nextui-org/ripple": "2.0.30", + "@nextui-org/shared-utils": "2.0.5", + "@nextui-org/spinner": "2.0.30", + "@nextui-org/use-aria-button": "2.0.9", + "@react-aria/button": "3.9.5", + "@react-aria/focus": "3.17.1", + "@react-aria/interactions": "3.21.3", + "@react-aria/utils": "3.24.1", + "@react-types/button": "3.9.4", + "@react-types/shared": "3.23.1" + }, + "peerDependencies": { + "@nextui-org/system": ">=2.0.0", + "@nextui-org/theme": ">=2.1.0", + "framer-motion": ">=10.17.0", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@nextui-org/calendar": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@nextui-org/calendar/-/calendar-2.0.7.tgz", + "integrity": "sha512-6mdgKJSl6tWo68FJQB1txSTRQ6/6+c3hipDYvzqDZRc+NbOJ3VevbFaPj5673JxeI2J5SyHLY2AEVw4q6HfaNw==", + "dependencies": { + "@internationalized/date": "^3.5.4", + "@nextui-org/button": "2.0.34", + "@nextui-org/framer-utils": "2.0.21", + "@nextui-org/react-utils": "2.0.14", + "@nextui-org/shared-icons": "2.0.8", + "@nextui-org/shared-utils": "2.0.5", + "@nextui-org/use-aria-button": "2.0.9", + "@react-aria/calendar": "3.5.8", + "@react-aria/focus": "3.17.1", + "@react-aria/i18n": "3.11.1", + "@react-aria/interactions": "3.21.3", + "@react-aria/utils": "3.24.1", + "@react-aria/visually-hidden": "3.8.12", + "@react-stately/calendar": "3.5.1", + "@react-stately/utils": "3.10.1", + "@react-types/button": "3.9.4", + "@react-types/calendar": "3.4.6", + "@react-types/shared": "3.23.1", + "@types/lodash.debounce": "^4.0.7", + "lodash.debounce": "^4.0.8", + "scroll-into-view-if-needed": "3.0.10" + }, + "peerDependencies": { + "@nextui-org/system": ">=2.1.0", + "@nextui-org/theme": ">=2.2.0", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@nextui-org/card": { + "version": "2.0.31", + "resolved": "https://registry.npmjs.org/@nextui-org/card/-/card-2.0.31.tgz", + "integrity": "sha512-KXeI4xu0HVOgC2sNBxv+OGbzYy+kA6HbsDB677j3R+MhyCrqCLsE5ahkn7FRWgIJAzoDkcHSunmc+q9ApoSWig==", + "dependencies": { + "@nextui-org/react-utils": "2.0.14", + "@nextui-org/ripple": "2.0.30", + "@nextui-org/shared-utils": "2.0.5", + "@nextui-org/use-aria-button": "2.0.9", + "@react-aria/button": "3.9.5", + "@react-aria/focus": "3.17.1", + "@react-aria/interactions": "3.21.3", + "@react-aria/utils": "3.24.1", + "@react-types/shared": "3.23.1" + }, + "peerDependencies": { + "@nextui-org/system": ">=2.0.0", + "@nextui-org/theme": ">=2.1.0", + "framer-motion": ">=10.17.0", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@nextui-org/checkbox": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@nextui-org/checkbox/-/checkbox-2.1.2.tgz", + "integrity": "sha512-0C5xcYcBMM/iAva3/fFYIvUiy91guV+mehUwRcPIxEFLA9bIOdOdGTkoAXlVcGCLIuYvlPiqSH0gShXvscOlNQ==", + "dependencies": { + "@nextui-org/react-utils": "2.0.14", + "@nextui-org/shared-utils": "2.0.5", + "@nextui-org/use-callback-ref": "2.0.5", + "@nextui-org/use-safe-layout-effect": "2.0.5", + "@react-aria/checkbox": "3.14.3", + "@react-aria/focus": "3.17.1", + "@react-aria/interactions": "3.21.3", + "@react-aria/utils": "3.24.1", + "@react-aria/visually-hidden": "3.8.12", + "@react-stately/checkbox": "3.6.5", + "@react-stately/toggle": "3.7.4", + "@react-types/checkbox": "3.8.1", + "@react-types/shared": "3.23.1" + }, + "peerDependencies": { + "@nextui-org/system": ">=2.0.0", + "@nextui-org/theme": ">=2.1.0", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@nextui-org/chip": { + "version": "2.0.30", + "resolved": "https://registry.npmjs.org/@nextui-org/chip/-/chip-2.0.30.tgz", + "integrity": "sha512-u/PbKFW8pGoPzBh8dDRvhBSdhX30lJbscQJvXzmCKHpSvK8rvBG1kHtOJEJ4fiuXbo/O0CYwZVAi03XloyOCdQ==", + "dependencies": { + "@nextui-org/react-utils": "2.0.14", + "@nextui-org/shared-icons": "2.0.8", + "@nextui-org/shared-utils": "2.0.5", + "@react-aria/focus": "3.17.1", + "@react-aria/interactions": "3.21.3", + "@react-aria/utils": "3.24.1", + "@react-types/checkbox": "3.8.1" + }, + "peerDependencies": { + "@nextui-org/system": ">=2.0.0", + "@nextui-org/theme": ">=2.1.0", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@nextui-org/code": { + "version": "2.0.29", + "resolved": "https://registry.npmjs.org/@nextui-org/code/-/code-2.0.29.tgz", + "integrity": "sha512-+aevUjVJxSkJ4Un/O3rBdI1NfHikatzDK6iD6nqWDCDR/I+9a5m+s3N8yuNt/Mt8jGKg0KEklPh3deYfCVCXdg==", + "dependencies": { + "@nextui-org/react-utils": "2.0.14", + "@nextui-org/shared-utils": "2.0.5", + "@nextui-org/system-rsc": "2.1.2" + }, + "peerDependencies": { + "@nextui-org/theme": ">=2.1.0", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@nextui-org/date-input": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@nextui-org/date-input/-/date-input-2.1.1.tgz", + "integrity": "sha512-fts8R058AVN8dhkBGaJ/7F68ZwM/E3Imu5uhauHoXVoJhaXNft5fA23HJYpNkFrG0k/Tk7vGcGSPistiERQuKg==", + "dependencies": { + "@internationalized/date": "^3.5.4", + "@nextui-org/react-utils": "2.0.14", + "@nextui-org/shared-utils": "2.0.5", + "@react-aria/datepicker": "3.10.1", + "@react-aria/i18n": "3.11.1", + "@react-aria/utils": "3.24.1", + "@react-stately/datepicker": "3.9.4", + "@react-types/datepicker": "3.7.4", + "@react-types/shared": "3.23.1" + }, + "peerDependencies": { + "@nextui-org/system": ">=2.1.0", + "@nextui-org/theme": ">=2.2.0", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@nextui-org/date-picker": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@nextui-org/date-picker/-/date-picker-2.1.2.tgz", + "integrity": "sha512-gNqhyA85SDtGNdr2CUBJ5FSy/wCtj2AKJGs2yEvKtA9A66khOH2H0tdfGALOWoAQdxGgOvP7c+9U5Oadogoygg==", + "dependencies": { + "@internationalized/date": "^3.5.4", + "@nextui-org/aria-utils": "2.0.21", + "@nextui-org/button": "2.0.34", + "@nextui-org/calendar": "2.0.7", + "@nextui-org/date-input": "2.1.1", + "@nextui-org/popover": "2.1.24", + "@nextui-org/react-utils": "2.0.14", + "@nextui-org/shared-icons": "2.0.8", + "@nextui-org/shared-utils": "2.0.5", + "@react-aria/datepicker": "3.10.1", + "@react-aria/i18n": "3.11.1", + "@react-aria/utils": "3.24.1", + "@react-stately/datepicker": "3.9.4", + "@react-stately/overlays": "3.6.7", + "@react-stately/utils": "3.10.1", + "@react-types/datepicker": "3.7.4", + "@react-types/shared": "3.23.1" + }, + "peerDependencies": { + "@nextui-org/system": ">=2.1.0", + "@nextui-org/theme": ">=2.2.0", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@nextui-org/divider": { + "version": "2.0.28", + "resolved": "https://registry.npmjs.org/@nextui-org/divider/-/divider-2.0.28.tgz", + "integrity": "sha512-IskKmDOO8qwmTO2WtDmrH8fZvnV2JebP3PFfwqpToAdDRbRUs78pls2e8/T9clbLLtNxjfCFAI/Yi9C+LPPEXw==", + "dependencies": { + "@nextui-org/react-rsc-utils": "2.0.12", + "@nextui-org/shared-utils": "2.0.5", + "@nextui-org/system-rsc": "2.1.2", + "@react-types/shared": "3.22.1" + }, + "peerDependencies": { + "@nextui-org/theme": ">=2.1.0", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@nextui-org/divider/node_modules/@react-types/shared": { + "version": "3.22.1", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.22.1.tgz", + "integrity": "sha512-PCpa+Vo6BKnRMuOEzy5zAZ3/H5tnQg1e80khMhK2xys0j6ZqzkgQC+fHMNZ7VDFNLqqNMj/o0eVeSBDh2POjkw==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@nextui-org/dropdown": { + "version": "2.1.26", + "resolved": "https://registry.npmjs.org/@nextui-org/dropdown/-/dropdown-2.1.26.tgz", + "integrity": "sha512-rPrn8hN7v2nLm9OJKagvf7AivsCAT0EWUcgWGaf5GVdwGJ65TZpjR18eAOyKBZRe5cdZ+FV6qqnavGVhD3458w==", + "dependencies": { + "@nextui-org/aria-utils": "2.0.21", + "@nextui-org/menu": "2.0.25", + "@nextui-org/popover": "2.1.24", + "@nextui-org/react-utils": "2.0.14", + "@nextui-org/shared-utils": "2.0.5", + "@react-aria/focus": "3.17.1", + "@react-aria/menu": "3.14.1", + "@react-aria/utils": "3.24.1", + "@react-stately/menu": "3.7.1", + "@react-types/menu": "3.9.9" + }, + "peerDependencies": { + "@nextui-org/system": ">=2.0.0", + "@nextui-org/theme": ">=2.1.0", + "framer-motion": ">=10.17.0", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@nextui-org/framer-utils": { + "version": "2.0.21", + "resolved": "https://registry.npmjs.org/@nextui-org/framer-utils/-/framer-utils-2.0.21.tgz", + "integrity": "sha512-kZzkaAHbtuBl85mivZ1WKVCcwdk8Z2NDmJiIpaLy16yliLNV1tnhoDOzRrxhv+6cbkKftx21tRrpImB4AyeqLw==", + "dependencies": { + "@nextui-org/shared-utils": "2.0.5", + "@nextui-org/system": "2.2.2", + "@nextui-org/use-measure": "2.0.1" + }, + "peerDependencies": { + "framer-motion": ">=10.17.0", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@nextui-org/framer-utils/node_modules/@nextui-org/system": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@nextui-org/system/-/system-2.2.2.tgz", + "integrity": "sha512-u30lWSIO4Q7DStiK5tJjDgKBQtmODeQZcC6llz973sJ9QlE4GeC1fgu0+/zXL8AZZ8o/iEXhHWXsZIJ26EquUQ==", + "dependencies": { + "@internationalized/date": "^3.5.4", + "@nextui-org/react-utils": "2.0.14", + "@nextui-org/system-rsc": "2.1.2", + "@react-aria/i18n": "3.11.1", + "@react-aria/overlays": "3.22.1", + "@react-aria/utils": "3.24.1", + "@react-stately/utils": "3.10.1" + }, + "peerDependencies": { + "framer-motion": ">=10.17.0", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@nextui-org/image": { + "version": "2.0.29", + "resolved": "https://registry.npmjs.org/@nextui-org/image/-/image-2.0.29.tgz", + "integrity": "sha512-w8MneV/JNUTCJUcIZcxtUYw1ZEZqlpezcCgGLr0cH3vp5pa+BZ9SdptwAL2wFoJAG8xk+et9fMXTROvF4h5W1g==", + "dependencies": { + "@nextui-org/react-utils": "2.0.14", + "@nextui-org/shared-utils": "2.0.5", + "@nextui-org/use-image": "2.0.5" + }, + "peerDependencies": { + "@nextui-org/system": ">=2.0.0", + "@nextui-org/theme": ">=2.1.0", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@nextui-org/input": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@nextui-org/input/-/input-2.2.2.tgz", + "integrity": "sha512-mCcFsObJdlCWMuSutKTRniFIDX5+z4BAAtt/XI1uzOtUO6WXgT97BwVzMihC1l14WQsw9TCwFKAl8JWdolkNCA==", + "dependencies": { + "@nextui-org/react-utils": "2.0.14", + "@nextui-org/shared-icons": "2.0.8", + "@nextui-org/shared-utils": "2.0.5", + "@nextui-org/use-safe-layout-effect": "2.0.5", + "@react-aria/focus": "3.17.1", + "@react-aria/interactions": "3.21.3", + "@react-aria/textfield": "3.14.5", + "@react-aria/utils": "3.24.1", + "@react-stately/utils": "3.10.1", + "@react-types/shared": "3.23.1", + "@react-types/textfield": "3.9.3", + "react-textarea-autosize": "^8.5.3" + }, + "peerDependencies": { + "@nextui-org/system": ">=2.0.0", + "@nextui-org/theme": ">=2.1.0", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@nextui-org/kbd": { + "version": "2.0.30", + "resolved": "https://registry.npmjs.org/@nextui-org/kbd/-/kbd-2.0.30.tgz", + "integrity": "sha512-rQw71noVUIRPf8N/Z5hdIGCtjFEVZO9xs2JVkiusKDxbGXFWKxJ3sTFzEY4VyLtORt2mEOQEWh26wbTnNjJzMw==", + "dependencies": { + "@nextui-org/react-utils": "2.0.14", + "@nextui-org/shared-utils": "2.0.5", + "@nextui-org/system-rsc": "2.1.2", + "@react-aria/utils": "3.24.1" + }, + "peerDependencies": { + "@nextui-org/theme": ">=2.1.0", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@nextui-org/link": { + "version": "2.0.32", + "resolved": "https://registry.npmjs.org/@nextui-org/link/-/link-2.0.32.tgz", + "integrity": "sha512-NIG8Ay/WfFxwMYKB11xg0iVAzJR1jy0QrtKFGaZscyJ522beM+aMBZuourC9u7kwjucTvt5fuGRm86KBVDBXCQ==", + "dependencies": { + "@nextui-org/react-utils": "2.0.14", + "@nextui-org/shared-icons": "2.0.8", + "@nextui-org/shared-utils": "2.0.5", + "@nextui-org/use-aria-link": "2.0.18", + "@react-aria/focus": "3.17.1", + "@react-aria/link": "3.7.1", + "@react-aria/utils": "3.24.1", + "@react-types/link": "3.5.5" + }, + "peerDependencies": { + "@nextui-org/system": ">=2.0.0", + "@nextui-org/theme": ">=2.1.0", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@nextui-org/listbox": { + "version": "2.1.22", + "resolved": "https://registry.npmjs.org/@nextui-org/listbox/-/listbox-2.1.22.tgz", + "integrity": "sha512-VFULRE7BBpNnXulhySHlENRiRUP7KdpozJfKM3X2kIwWoFekO8DDUT8RiLj2PyDtGjKam74ghHhMuAFXFhVQ+g==", + "dependencies": { + "@nextui-org/aria-utils": "2.0.21", + "@nextui-org/divider": "2.0.28", + "@nextui-org/react-utils": "2.0.14", + "@nextui-org/shared-utils": "2.0.5", + "@nextui-org/use-is-mobile": "2.0.8", + "@react-aria/focus": "3.17.1", + "@react-aria/interactions": "3.21.3", + "@react-aria/listbox": "3.12.1", + "@react-aria/utils": "3.24.1", + "@react-stately/list": "3.10.5", + "@react-types/menu": "3.9.9", + "@react-types/shared": "3.23.1" + }, + "peerDependencies": { + "@nextui-org/system": ">=2.0.0", + "@nextui-org/theme": ">=2.1.0", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@nextui-org/menu": { + "version": "2.0.25", + "resolved": "https://registry.npmjs.org/@nextui-org/menu/-/menu-2.0.25.tgz", + "integrity": "sha512-VkCaaq19JKNjIgg4bmGebzHkSV1A3C1CRV5w5qRPg5AI59pdWlbMLpllm5mPqz+U0R0P5saGfCfEfcC0LrCFdQ==", + "dependencies": { + "@nextui-org/aria-utils": "2.0.21", + "@nextui-org/divider": "2.0.28", + "@nextui-org/react-utils": "2.0.14", + "@nextui-org/shared-utils": "2.0.5", + "@nextui-org/use-aria-menu": "2.0.5", + "@nextui-org/use-is-mobile": "2.0.8", + "@react-aria/focus": "3.17.1", + "@react-aria/interactions": "3.21.3", + "@react-aria/menu": "3.14.1", + "@react-aria/utils": "3.24.1", + "@react-stately/menu": "3.7.1", + "@react-stately/tree": "3.8.1", + "@react-types/menu": "3.9.9", + "@react-types/shared": "3.23.1" + }, + "peerDependencies": { + "@nextui-org/system": ">=2.0.0", + "@nextui-org/theme": ">=2.1.0", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@nextui-org/modal": { + "version": "2.0.36", + "resolved": "https://registry.npmjs.org/@nextui-org/modal/-/modal-2.0.36.tgz", + "integrity": "sha512-ucWBobeoM8BVLpgXrtZ/H5TD9eFS2YF4W7vntWC05Q13A34LSHgBjNHJkfwW/OebGjJoaDoRiIBohWaiyyliTA==", + "dependencies": { + "@nextui-org/framer-utils": "2.0.21", + "@nextui-org/react-utils": "2.0.14", + "@nextui-org/shared-icons": "2.0.8", + "@nextui-org/shared-utils": "2.0.5", + "@nextui-org/use-aria-button": "2.0.9", + "@nextui-org/use-aria-modal-overlay": "2.0.10", + "@nextui-org/use-disclosure": "2.0.9", + "@react-aria/dialog": "3.5.14", + "@react-aria/focus": "3.17.1", + "@react-aria/interactions": "3.21.3", + "@react-aria/overlays": "3.22.1", + "@react-aria/utils": "3.24.1", + "@react-stately/overlays": "3.6.7", + "@react-types/overlays": "3.8.7" + }, + "peerDependencies": { + "@nextui-org/system": ">=2.0.0", + "@nextui-org/theme": ">=2.1.0", + "framer-motion": ">=10.17.0", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@nextui-org/navbar": { + "version": "2.0.33", + "resolved": "https://registry.npmjs.org/@nextui-org/navbar/-/navbar-2.0.33.tgz", + "integrity": "sha512-WbPLEz6yE1vxKTqZDN85YPCWR/JSvpOO604xBpaaCf+OLfEsb+herz7+GDPnvHKaPDASoxU5WaSQJR9nrJ/YHg==", + "dependencies": { + "@nextui-org/framer-utils": "2.0.21", + "@nextui-org/react-utils": "2.0.14", + "@nextui-org/shared-utils": "2.0.5", + "@nextui-org/use-aria-toggle-button": "2.0.9", + "@nextui-org/use-scroll-position": "2.0.6", + "@react-aria/focus": "3.17.1", + "@react-aria/interactions": "3.21.3", + "@react-aria/overlays": "3.22.1", + "@react-aria/utils": "3.24.1", + "@react-stately/toggle": "3.7.4", + "@react-stately/utils": "3.10.1", + "react-remove-scroll": "^2.5.6" + }, + "peerDependencies": { + "@nextui-org/system": ">=2.0.0", + "@nextui-org/theme": ">=2.1.0", + "framer-motion": ">=10.17.0", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@nextui-org/pagination": { + "version": "2.0.33", + "resolved": "https://registry.npmjs.org/@nextui-org/pagination/-/pagination-2.0.33.tgz", + "integrity": "sha512-LiDDTSTuC0Q9gSI1gc/b+lmKR8/zFiwSfYjLh7KDND3m+qE44waICWnK1U7P6Y999Nu1LwaGSGtqayd326aPrg==", + "dependencies": { + "@nextui-org/react-utils": "2.0.14", + "@nextui-org/shared-icons": "2.0.8", + "@nextui-org/shared-utils": "2.0.5", + "@nextui-org/use-pagination": "2.0.7", + "@react-aria/focus": "3.17.1", + "@react-aria/i18n": "3.11.1", + "@react-aria/interactions": "3.21.3", + "@react-aria/utils": "3.24.1", + "scroll-into-view-if-needed": "3.0.10" + }, + "peerDependencies": { + "@nextui-org/system": ">=2.0.0", + "@nextui-org/theme": ">=2.1.0", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@nextui-org/popover": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/@nextui-org/popover/-/popover-2.1.24.tgz", + "integrity": "sha512-PGbTxdcc06BMxEd/HYsL0sVa0fdGjHPYNSvcSSM0KA6Fh98pznO9DoQHjIEPAul87yEwl7cDDj7mANcdK9BVnA==", + "dependencies": { + "@nextui-org/aria-utils": "2.0.21", + "@nextui-org/button": "2.0.34", + "@nextui-org/framer-utils": "2.0.21", + "@nextui-org/react-utils": "2.0.14", + "@nextui-org/shared-utils": "2.0.5", + "@nextui-org/use-aria-button": "2.0.9", + "@nextui-org/use-safe-layout-effect": "2.0.5", + "@react-aria/dialog": "3.5.14", + "@react-aria/focus": "3.17.1", + "@react-aria/interactions": "3.21.3", + "@react-aria/overlays": "3.22.1", + "@react-aria/utils": "3.24.1", + "@react-stately/overlays": "3.6.7", + "@react-types/button": "3.9.4", + "@react-types/overlays": "3.8.7", + "react-remove-scroll": "^2.5.6" + }, + "peerDependencies": { + "@nextui-org/system": ">=2.0.0", + "@nextui-org/theme": ">=2.1.0", + "framer-motion": ">=10.17.0", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@nextui-org/progress": { + "version": "2.0.31", + "resolved": "https://registry.npmjs.org/@nextui-org/progress/-/progress-2.0.31.tgz", + "integrity": "sha512-ZFjV4068gYPe9S4R1e/8oqwtPFKd9ag8RB0JoToq55AM5aLItOA/Q/uwBnDz7ait3C7viWawcN4leW1C8dSurQ==", + "dependencies": { + "@nextui-org/react-utils": "2.0.14", + "@nextui-org/shared-utils": "2.0.5", + "@nextui-org/use-is-mounted": "2.0.5", + "@react-aria/i18n": "3.11.1", + "@react-aria/progress": "3.4.13", + "@react-aria/utils": "3.24.1", + "@react-types/progress": "3.5.4" + }, + "peerDependencies": { + "@nextui-org/system": ">=2.0.0", + "@nextui-org/theme": ">=2.1.0", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@nextui-org/radio": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@nextui-org/radio/-/radio-2.1.2.tgz", + "integrity": "sha512-JcWKRqXXRwQtz5ABzykuu+S4/8cO9GKa21Gget1fdo/iSDcUtGDHIf6wlpvWSNekpvIERZd9UdpwhaXWbD4pOg==", + "dependencies": { + "@nextui-org/react-utils": "2.0.14", + "@nextui-org/shared-utils": "2.0.5", + "@react-aria/focus": "3.17.1", + "@react-aria/interactions": "3.21.3", + "@react-aria/radio": "3.10.4", + "@react-aria/utils": "3.24.1", + "@react-aria/visually-hidden": "3.8.12", + "@react-stately/radio": "3.10.4", + "@react-types/radio": "3.8.1", + "@react-types/shared": "3.23.1" + }, + "peerDependencies": { + "@nextui-org/system": ">=2.0.0", + "@nextui-org/theme": ">=2.1.0", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@nextui-org/react": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@nextui-org/react/-/react-2.4.2.tgz", + "integrity": "sha512-g7CqAX/x0DJsIUmD+Z6I4T1699uVmu5kbuY0n1PdA4IDjFSKzgkMCIogcIKu2iUV+LVlvfF1lDhs300OIpouig==", + "dependencies": { + "@nextui-org/accordion": "2.0.35", + "@nextui-org/autocomplete": "2.1.2", + "@nextui-org/avatar": "2.0.30", + "@nextui-org/badge": "2.0.29", + "@nextui-org/breadcrumbs": "2.0.10", + "@nextui-org/button": "2.0.34", + "@nextui-org/calendar": "2.0.7", + "@nextui-org/card": "2.0.31", + "@nextui-org/checkbox": "2.1.2", + "@nextui-org/chip": "2.0.30", + "@nextui-org/code": "2.0.29", + "@nextui-org/date-input": "2.1.1", + "@nextui-org/date-picker": "2.1.2", + "@nextui-org/divider": "2.0.28", + "@nextui-org/dropdown": "2.1.26", + "@nextui-org/framer-utils": "2.0.21", + "@nextui-org/image": "2.0.29", + "@nextui-org/input": "2.2.2", + "@nextui-org/kbd": "2.0.30", + "@nextui-org/link": "2.0.32", + "@nextui-org/listbox": "2.1.22", + "@nextui-org/menu": "2.0.25", + "@nextui-org/modal": "2.0.36", + "@nextui-org/navbar": "2.0.33", + "@nextui-org/pagination": "2.0.33", + "@nextui-org/popover": "2.1.24", + "@nextui-org/progress": "2.0.31", + "@nextui-org/radio": "2.1.2", + "@nextui-org/ripple": "2.0.30", + "@nextui-org/scroll-shadow": "2.1.17", + "@nextui-org/select": "2.2.2", + "@nextui-org/skeleton": "2.0.29", + "@nextui-org/slider": "2.2.12", + "@nextui-org/snippet": "2.0.38", + "@nextui-org/spacer": "2.0.29", + "@nextui-org/spinner": "2.0.30", + "@nextui-org/switch": "2.0.31", + "@nextui-org/system": "2.2.2", + "@nextui-org/table": "2.0.36", + "@nextui-org/tabs": "2.0.32", + "@nextui-org/theme": "2.2.6", + "@nextui-org/tooltip": "2.0.36", + "@nextui-org/user": "2.0.31", + "@react-aria/visually-hidden": "3.8.12" + }, + "peerDependencies": { + "framer-motion": ">=10.17.0", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@nextui-org/react-rsc-utils": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/@nextui-org/react-rsc-utils/-/react-rsc-utils-2.0.12.tgz", + "integrity": "sha512-s2IG4pM1K+kbm6A2g3UpqrS592AExpGixtZNPJ2lV5+UQi1ld3vb4EiBIOViZMoSCNCoNdaeO5Yqo6cKghwCPA==" + }, + "node_modules/@nextui-org/react-utils": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/@nextui-org/react-utils/-/react-utils-2.0.14.tgz", + "integrity": "sha512-fed97WSaHt8/sC5F4DFTVj25YQsepFGDyudommPGQsTksQ6GQkMITuHckzAyPiTTuWHSW/GZykvVVAlK9hS5Wg==", + "dependencies": { + "@nextui-org/react-rsc-utils": "2.0.12", + "@nextui-org/shared-utils": "2.0.5" + }, + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@nextui-org/react/node_modules/@nextui-org/system": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@nextui-org/system/-/system-2.2.2.tgz", + "integrity": "sha512-u30lWSIO4Q7DStiK5tJjDgKBQtmODeQZcC6llz973sJ9QlE4GeC1fgu0+/zXL8AZZ8o/iEXhHWXsZIJ26EquUQ==", + "dependencies": { + "@internationalized/date": "^3.5.4", + "@nextui-org/react-utils": "2.0.14", + "@nextui-org/system-rsc": "2.1.2", + "@react-aria/i18n": "3.11.1", + "@react-aria/overlays": "3.22.1", + "@react-aria/utils": "3.24.1", + "@react-stately/utils": "3.10.1" + }, + "peerDependencies": { + "framer-motion": ">=10.17.0", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@nextui-org/react/node_modules/@nextui-org/theme": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@nextui-org/theme/-/theme-2.2.6.tgz", + "integrity": "sha512-FyDp5edpmjbvPzRx+D2+Km1oZ73wQOzKMSBPomOgP0h9OFnnTHqKlmtbGhWSk2cEyYN9VsaGvqJTw8X35/aChQ==", + "dependencies": { + "clsx": "^1.2.1", + "color": "^4.2.3", + "color2k": "^2.0.2", + "deepmerge": "4.3.1", + "flat": "^5.0.2", + "lodash.foreach": "^4.5.0", + "lodash.get": "^4.4.2", + "lodash.kebabcase": "^4.1.1", + "lodash.mapkeys": "^4.6.0", + "lodash.omit": "^4.5.0", + "tailwind-merge": "^1.14.0", + "tailwind-variants": "^0.1.20" + }, + "peerDependencies": { + "tailwindcss": ">=3.4.0" + } + }, + "node_modules/@nextui-org/react/node_modules/clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/@nextui-org/react/node_modules/tailwind-merge": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-1.14.0.tgz", + "integrity": "sha512-3mFKyCo/MBcgyOTlrY8T7odzZFx+w+qKSMAmdFzRvqBfLlSigU6TZnlFHK0lkMwj9Bj8OYU+9yW9lmGuS0QEnQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/@nextui-org/ripple": { + "version": "2.0.30", + "resolved": "https://registry.npmjs.org/@nextui-org/ripple/-/ripple-2.0.30.tgz", + "integrity": "sha512-GmHwC+F2JIYQAeFuwtFbdE6av8lzOJVdA5yops9vhhzeBPT33dMjgazCn0HZT5TvP0gX+xxT/74ONE0ik0Kayg==", + "dependencies": { + "@nextui-org/react-utils": "2.0.14", + "@nextui-org/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@nextui-org/system": ">=2.0.0", + "@nextui-org/theme": ">=2.1.0", + "framer-motion": ">=10.17.0", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@nextui-org/scroll-shadow": { + "version": "2.1.17", + "resolved": "https://registry.npmjs.org/@nextui-org/scroll-shadow/-/scroll-shadow-2.1.17.tgz", + "integrity": "sha512-JOJc6nbdFHcMn/zpaf78AAZ8Vwo/iQO6iWJVHlN6ROjSKL7EImP/V78m14Y+kd0hkzU8CcHswdpmCefaioFlRA==", + "dependencies": { + "@nextui-org/react-utils": "2.0.14", + "@nextui-org/shared-utils": "2.0.5", + "@nextui-org/use-data-scroll-overflow": "2.1.4" + }, + "peerDependencies": { + "@nextui-org/system": ">=2.0.0", + "@nextui-org/theme": ">=2.1.0", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@nextui-org/select": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@nextui-org/select/-/select-2.2.2.tgz", + "integrity": "sha512-bCk6/LJAhhSM5VXiny7rDTH5f7ri7mGKx4V+K83kY9uW01ioWWYId1EhbP6Crd9PSvmQL42mhId/5dLRxgUimA==", + "dependencies": { + "@nextui-org/aria-utils": "2.0.21", + "@nextui-org/listbox": "2.1.22", + "@nextui-org/popover": "2.1.24", + "@nextui-org/react-utils": "2.0.14", + "@nextui-org/scroll-shadow": "2.1.17", + "@nextui-org/shared-icons": "2.0.8", + "@nextui-org/shared-utils": "2.0.5", + "@nextui-org/spinner": "2.0.30", + "@nextui-org/use-aria-button": "2.0.9", + "@nextui-org/use-aria-multiselect": "2.2.2", + "@nextui-org/use-safe-layout-effect": "2.0.5", + "@react-aria/focus": "3.17.1", + "@react-aria/form": "3.0.5", + "@react-aria/interactions": "3.21.3", + "@react-aria/utils": "3.24.1", + "@react-aria/visually-hidden": "3.8.12", + "@react-types/shared": "3.23.1" + }, + "peerDependencies": { + "@nextui-org/system": ">=2.0.0", + "@nextui-org/theme": ">=2.1.0", + "framer-motion": ">=10.17.0", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@nextui-org/shared-icons": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@nextui-org/shared-icons/-/shared-icons-2.0.8.tgz", + "integrity": "sha512-siKuw+CN03cB2N1eUpIleP+lTpjM4gSmcco7RXTpXiwXJXlxjKo4N8gQYS04HCBXm9QMWgyngvUEt2II9NYyrw==", + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@nextui-org/shared-utils": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nextui-org/shared-utils/-/shared-utils-2.0.5.tgz", + "integrity": "sha512-aFc/CUL8RVfBh0IotIpxkpKjyUPc/zJaMJd5pRCQA1kIpKLdSrlh3//MLYMaP/fo/NQtE3DPeXqfKhHRr1fkEw==" + }, + "node_modules/@nextui-org/skeleton": { + "version": "2.0.29", + "resolved": "https://registry.npmjs.org/@nextui-org/skeleton/-/skeleton-2.0.29.tgz", + "integrity": "sha512-s/oQdUc1Ao7XRmUP82V2/hI3B644ZQzIYuPIgp+A6DyDLfyRUx8PLWN/EhN5Ku2M/s6WYTkwulDrKeo4dlMsrw==", + "dependencies": { + "@nextui-org/react-utils": "2.0.14", + "@nextui-org/shared-utils": "2.0.5" + }, + "peerDependencies": { + "@nextui-org/system": ">=2.0.0", + "@nextui-org/theme": ">=2.1.0", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@nextui-org/slider": { + "version": "2.2.12", + "resolved": "https://registry.npmjs.org/@nextui-org/slider/-/slider-2.2.12.tgz", + "integrity": "sha512-5+72YlWxV6bm9hGNpWN5G+6OeqU7S9N2ECwEdO4COQ1hvMiimiJ3lrTUHIS2AvKimEpw+MLkUoKIbqAV23zxuw==", + "dependencies": { + "@nextui-org/react-utils": "2.0.14", + "@nextui-org/shared-utils": "2.0.5", + "@nextui-org/tooltip": "2.0.36", + "@react-aria/focus": "3.17.1", + "@react-aria/i18n": "3.11.1", + "@react-aria/interactions": "3.21.3", + "@react-aria/slider": "3.7.8", + "@react-aria/utils": "3.24.1", + "@react-aria/visually-hidden": "3.8.12", + "@react-stately/slider": "3.5.4" + }, + "peerDependencies": { + "@nextui-org/system": ">=2.0.0", + "@nextui-org/theme": ">=2.1.0", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@nextui-org/snippet": { + "version": "2.0.38", + "resolved": "https://registry.npmjs.org/@nextui-org/snippet/-/snippet-2.0.38.tgz", + "integrity": "sha512-8lMqtB1KQtMkpZFb3x/T42zdZ+QqcGr6d/yVE+zKzyEd+xqzm2g/hDpPqy0Mf5JaC1Z+lXoRzF/6XbD99FCEbw==", + "dependencies": { + "@nextui-org/button": "2.0.34", + "@nextui-org/react-utils": "2.0.14", + "@nextui-org/shared-icons": "2.0.8", + "@nextui-org/shared-utils": "2.0.5", + "@nextui-org/tooltip": "2.0.36", + "@nextui-org/use-clipboard": "2.0.5", + "@react-aria/focus": "3.17.1", + "@react-aria/utils": "3.24.1" + }, + "peerDependencies": { + "@nextui-org/system": ">=2.0.0", + "@nextui-org/theme": ">=2.1.0", + "framer-motion": ">=10.17.0", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@nextui-org/spacer": { + "version": "2.0.29", + "resolved": "https://registry.npmjs.org/@nextui-org/spacer/-/spacer-2.0.29.tgz", + "integrity": "sha512-lcgzHIvTXXllnM6MMjti0ub8jEx9jmtzdd5+zgFAHLTeDS3pDffNZndmU+RkzpyGSyK20PCrMkV/sB4SCDN1KA==", + "dependencies": { + "@nextui-org/react-utils": "2.0.14", + "@nextui-org/shared-utils": "2.0.5", + "@nextui-org/system-rsc": "2.1.2" + }, + "peerDependencies": { + "@nextui-org/theme": ">=2.1.0", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@nextui-org/spinner": { + "version": "2.0.30", + "resolved": "https://registry.npmjs.org/@nextui-org/spinner/-/spinner-2.0.30.tgz", + "integrity": "sha512-+oygL2dewHZzJiSUEIvzL0tIx+G+98mvO3ToFAMXaH0N3bOQNSiFDPwUHUx6PgAQ9pr9RKtdnb4ywstcG9j+Gg==", + "dependencies": { + "@nextui-org/react-utils": "2.0.14", + "@nextui-org/shared-utils": "2.0.5", + "@nextui-org/system-rsc": "2.1.2" + }, + "peerDependencies": { + "@nextui-org/theme": ">=2.1.0", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@nextui-org/switch": { + "version": "2.0.31", + "resolved": "https://registry.npmjs.org/@nextui-org/switch/-/switch-2.0.31.tgz", + "integrity": "sha512-WPHqWQfyISA8nmQ8ihaO5rIHm/K9nyfrV0Fxm6EcnFilTMZhh4Kt+p7FfJrZw+MMyzIEGFfMDySk1KVrMubc1g==", + "dependencies": { + "@nextui-org/react-utils": "2.0.14", + "@nextui-org/shared-utils": "2.0.5", + "@nextui-org/use-safe-layout-effect": "2.0.5", + "@react-aria/focus": "3.17.1", + "@react-aria/interactions": "3.21.3", + "@react-aria/switch": "3.6.4", + "@react-aria/utils": "3.24.1", + "@react-aria/visually-hidden": "3.8.12", + "@react-stately/toggle": "3.7.4", + "@react-types/shared": "3.23.1" + }, + "peerDependencies": { + "@nextui-org/system": ">=2.0.0", + "@nextui-org/theme": ">=2.1.0", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@nextui-org/system": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@nextui-org/system/-/system-2.2.1.tgz", + "integrity": "sha512-XBNf+8cB1S0OOfVg5jOCtcaL0yWCYT9M6N+MGLI9JSyvPlpN1eCsoy0qdW3yI124ECpkZD0xbqbeXvvQsEdkKQ==", + "dependencies": { + "@internationalized/date": "^3.5.2", + "@nextui-org/react-utils": "2.0.13", + "@nextui-org/system-rsc": "2.1.2", + "@react-aria/i18n": "3.10.2", + "@react-aria/overlays": "3.21.1", + "@react-aria/utils": "3.24.1", + "@react-stately/utils": "3.9.1" + }, + "peerDependencies": { + "framer-motion": ">=10.17.0", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@nextui-org/system-rsc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@nextui-org/system-rsc/-/system-rsc-2.1.2.tgz", + "integrity": "sha512-3F7pG68Ikh1JsMtRQqmyXAojAV4lMPCKCy0n8RiIxJkEJg11RGTXhnABHF2jP6uxMH/0q5zVzuFubQJfW++ISQ==", + "dependencies": { + "clsx": "^1.2.1" + }, + "peerDependencies": { + "@nextui-org/theme": ">=2.1.0", + "react": ">=18" + } + }, + "node_modules/@nextui-org/system-rsc/node_modules/clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/@nextui-org/system/node_modules/@nextui-org/react-utils": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/@nextui-org/react-utils/-/react-utils-2.0.13.tgz", + "integrity": "sha512-4DM1Cph1lVY64T/HDyEqcxYkInXx6hdL1Kp9StLza9yqgYmVipTaPkWZdmWbfkhP+dVVqrH3DVFfHtpLTQ625w==", + "dependencies": { + "@nextui-org/react-rsc-utils": "2.0.12", + "@nextui-org/shared-utils": "2.0.5" + }, + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@nextui-org/system/node_modules/@react-aria/i18n": { + "version": "3.10.2", + "resolved": "https://registry.npmjs.org/@react-aria/i18n/-/i18n-3.10.2.tgz", + "integrity": "sha512-Z1ormoIvMOI4mEdcFLYsoJy9w/EzBdBmgfLP+S/Ah+1xwQOXpgwZxiKOhYHpWa0lf6hkKJL34N9MHJvCJ5Crvw==", + "dependencies": { + "@internationalized/date": "^3.5.2", + "@internationalized/message": "^3.1.2", + "@internationalized/number": "^3.5.1", + "@internationalized/string": "^3.2.1", + "@react-aria/ssr": "^3.9.2", + "@react-aria/utils": "^3.23.2", + "@react-types/shared": "^3.22.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@nextui-org/system/node_modules/@react-aria/overlays": { + "version": "3.21.1", + "resolved": "https://registry.npmjs.org/@react-aria/overlays/-/overlays-3.21.1.tgz", + "integrity": "sha512-djEBDF+TbIIOHWWNpdm19+z8xtY8U+T+wKVQg/UZ6oWnclSqSWeGl70vu73Cg4HVBJ4hKf1SRx4Z/RN6VvH4Yw==", + "dependencies": { + "@react-aria/focus": "^3.16.2", + "@react-aria/i18n": "^3.10.2", + "@react-aria/interactions": "^3.21.1", + "@react-aria/ssr": "^3.9.2", + "@react-aria/utils": "^3.23.2", + "@react-aria/visually-hidden": "^3.8.10", + "@react-stately/overlays": "^3.6.5", + "@react-types/button": "^3.9.2", + "@react-types/overlays": "^3.8.5", + "@react-types/shared": "^3.22.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@nextui-org/system/node_modules/@react-stately/utils": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/@react-stately/utils/-/utils-3.9.1.tgz", + "integrity": "sha512-yzw75GE0iUWiyps02BOAPTrybcsMIxEJlzXqtvllAb01O9uX5n0i3X+u2eCpj2UoDF4zS08Ps0jPgWxg8xEYtA==", + "dependencies": { + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@nextui-org/table": { + "version": "2.0.36", + "resolved": "https://registry.npmjs.org/@nextui-org/table/-/table-2.0.36.tgz", + "integrity": "sha512-vpohZo5p3XmT6FLOKKwmm8SdCA/h2QPQz6Y66sAfHuoqAfkmfVfAeyKgYTe20pVJy3Whvyix6IA8e0eWETDTEw==", + "dependencies": { + "@nextui-org/checkbox": "2.1.2", + "@nextui-org/react-utils": "2.0.14", + "@nextui-org/shared-icons": "2.0.8", + "@nextui-org/shared-utils": "2.0.5", + "@nextui-org/spacer": "2.0.29", + "@react-aria/focus": "3.17.1", + "@react-aria/interactions": "3.21.3", + "@react-aria/table": "3.14.1", + "@react-aria/utils": "3.24.1", + "@react-aria/visually-hidden": "3.8.12", + "@react-stately/table": "3.11.8", + "@react-stately/virtualizer": "3.7.1", + "@react-types/grid": "3.2.6", + "@react-types/table": "3.9.5" + }, + "peerDependencies": { + "@nextui-org/system": ">=2.0.0", + "@nextui-org/theme": ">=2.1.0", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@nextui-org/tabs": { + "version": "2.0.32", + "resolved": "https://registry.npmjs.org/@nextui-org/tabs/-/tabs-2.0.32.tgz", + "integrity": "sha512-TVCwm1GI7rkf/o7+eWpklRQBTg2Y/m3eNBLU1jA+Ppqs+Mr31y7BHoNLqTZ6jpj59DA1OcpwbJH5xhGk0pOvwA==", + "dependencies": { + "@nextui-org/aria-utils": "2.0.21", + "@nextui-org/framer-utils": "2.0.21", + "@nextui-org/react-utils": "2.0.14", + "@nextui-org/shared-utils": "2.0.5", + "@nextui-org/use-is-mounted": "2.0.5", + "@nextui-org/use-update-effect": "2.0.5", + "@react-aria/focus": "3.17.1", + "@react-aria/interactions": "3.21.3", + "@react-aria/tabs": "3.9.1", + "@react-aria/utils": "3.24.1", + "@react-stately/tabs": "3.6.6", + "@react-types/shared": "3.23.1", + "@react-types/tabs": "3.3.7", + "scroll-into-view-if-needed": "3.0.10" + }, + "peerDependencies": { + "@nextui-org/system": ">=2.0.0", + "@nextui-org/theme": ">=2.1.0", + "framer-motion": ">=10.17.0", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@nextui-org/theme": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/@nextui-org/theme/-/theme-2.2.5.tgz", + "integrity": "sha512-xvlMUK/rTv95s9hvr/XWtyVTm3IHRR7gCHpGOhG9DsxkQFbSQDiznsBC7LPBFJ21o+idiv28Il6aroV3ua/BOA==", + "dependencies": { + "clsx": "^1.2.1", + "color": "^4.2.3", + "color2k": "^2.0.2", + "deepmerge": "4.3.1", + "flat": "^5.0.2", + "lodash.foreach": "^4.5.0", + "lodash.get": "^4.4.2", + "lodash.kebabcase": "^4.1.1", + "lodash.mapkeys": "^4.6.0", + "lodash.omit": "^4.5.0", + "tailwind-merge": "^1.14.0", + "tailwind-variants": "^0.1.20" + }, + "peerDependencies": { + "tailwindcss": ">=3.4.0" + } + }, + "node_modules/@nextui-org/theme/node_modules/clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/@nextui-org/theme/node_modules/tailwind-merge": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-1.14.0.tgz", + "integrity": "sha512-3mFKyCo/MBcgyOTlrY8T7odzZFx+w+qKSMAmdFzRvqBfLlSigU6TZnlFHK0lkMwj9Bj8OYU+9yW9lmGuS0QEnQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/@nextui-org/tooltip": { + "version": "2.0.36", + "resolved": "https://registry.npmjs.org/@nextui-org/tooltip/-/tooltip-2.0.36.tgz", + "integrity": "sha512-tV3BefTvmYzSC4TX+UPV7p3F5fs52sFzQ1/Try/Bkz5B1F9yXviO9dV2/pqXSfOJVvLVJS2RMi5wZkaYh1xtNw==", + "dependencies": { + "@nextui-org/aria-utils": "2.0.21", + "@nextui-org/framer-utils": "2.0.21", + "@nextui-org/react-utils": "2.0.14", + "@nextui-org/shared-utils": "2.0.5", + "@nextui-org/use-safe-layout-effect": "2.0.5", + "@react-aria/interactions": "3.21.3", + "@react-aria/overlays": "3.22.1", + "@react-aria/tooltip": "3.7.4", + "@react-aria/utils": "3.24.1", + "@react-stately/tooltip": "3.4.9", + "@react-types/overlays": "3.8.7", + "@react-types/tooltip": "3.4.9" + }, + "peerDependencies": { + "@nextui-org/system": ">=2.0.0", + "@nextui-org/theme": ">=2.1.0", + "framer-motion": ">=10.17.0", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@nextui-org/use-aria-accordion": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@nextui-org/use-aria-accordion/-/use-aria-accordion-2.0.6.tgz", + "integrity": "sha512-47+/gO67YufQUtL0f2TIdaa8++5EBtIK7Ltq1GpUat2qjbMFvIb6Ao/Jf3KHU5NicLLRnWPSK1vNaupkYwN/ew==", + "dependencies": { + "@react-aria/button": "3.9.5", + "@react-aria/focus": "3.17.1", + "@react-aria/selection": "3.18.1", + "@react-aria/utils": "3.24.1", + "@react-stately/tree": "3.8.1", + "@react-types/accordion": "3.0.0-alpha.21", + "@react-types/shared": "3.23.1" + }, + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@nextui-org/use-aria-button": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/@nextui-org/use-aria-button/-/use-aria-button-2.0.9.tgz", + "integrity": "sha512-5FjDl57/1Ey3MgJn+yB0/CPABsSVgXiE+jT7ZLnSqH9kmdXV/eMiuplF7fOOvaSMCA1cE3KCetaPVDIZoJI1/w==", + "dependencies": { + "@react-aria/focus": "3.17.1", + "@react-aria/interactions": "3.21.3", + "@react-aria/utils": "3.24.1", + "@react-types/button": "3.9.4", + "@react-types/shared": "3.23.1" + }, + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@nextui-org/use-aria-link": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/@nextui-org/use-aria-link/-/use-aria-link-2.0.18.tgz", + "integrity": "sha512-6ZIIOfMMGbSOF9FcJTPrsVOm2LP7OV+QwF0vYelZeEK5zFXb5f8e2J/fEbCVWKLPFDB2VyoBUDWMzRfrizixzg==", + "dependencies": { + "@react-aria/focus": "3.17.1", + "@react-aria/interactions": "3.21.3", + "@react-aria/utils": "3.24.1", + "@react-types/link": "3.5.5", + "@react-types/shared": "3.23.1" + }, + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@nextui-org/use-aria-menu": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nextui-org/use-aria-menu/-/use-aria-menu-2.0.5.tgz", + "integrity": "sha512-7bAwISb4vIGhAuvZEHpb/28u0k2/HxNhMJUcz/UxVJTMqSkbSJR2RKdm64WfhEq2A8ZtvED0BAJbDuPf4Q4avg==", + "dependencies": { + "@react-aria/i18n": "3.11.1", + "@react-aria/interactions": "3.21.3", + "@react-aria/menu": "3.14.1", + "@react-aria/selection": "3.18.1", + "@react-aria/utils": "3.24.1", + "@react-stately/collections": "3.10.7", + "@react-stately/tree": "3.8.1", + "@react-types/menu": "3.9.9", + "@react-types/shared": "3.23.1" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@nextui-org/use-aria-modal-overlay": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@nextui-org/use-aria-modal-overlay/-/use-aria-modal-overlay-2.0.10.tgz", + "integrity": "sha512-/VONX/beH4vu7SQjAtxcQoRhdAOro+QeBk9XOW+qcNvxZG4Em1vf1KFmpHRC40DtsrUk3I0cxaZezeIgfOZ41Q==", + "dependencies": { + "@react-aria/overlays": "3.22.1", + "@react-aria/utils": "3.24.1", + "@react-stately/overlays": "3.6.7", + "@react-types/shared": "3.23.1" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@nextui-org/use-aria-multiselect": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@nextui-org/use-aria-multiselect/-/use-aria-multiselect-2.2.2.tgz", + "integrity": "sha512-iFw9CVRWTKBl+c1FbbHxp4K0B6aQTXSzXiIP09TJ1NQ10fk1GQXBIhFUIyvIwRJRGvYAL+vwkgj39Ac1p1esJQ==", + "dependencies": { + "@react-aria/i18n": "3.11.1", + "@react-aria/interactions": "3.21.3", + "@react-aria/label": "3.7.8", + "@react-aria/listbox": "3.12.1", + "@react-aria/menu": "3.14.1", + "@react-aria/selection": "3.18.1", + "@react-aria/utils": "3.24.1", + "@react-stately/form": "3.0.3", + "@react-stately/list": "3.10.5", + "@react-stately/menu": "3.7.1", + "@react-types/button": "3.9.4", + "@react-types/overlays": "3.8.7", + "@react-types/select": "3.9.4", + "@react-types/shared": "3.23.1" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@nextui-org/use-aria-toggle-button": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/@nextui-org/use-aria-toggle-button/-/use-aria-toggle-button-2.0.9.tgz", + "integrity": "sha512-JpPD97tYpPwyhgXgJbWYgMDp5ZysM1LyvvmyHmq6BtvSpyYqQKU7V3LDXuirBEN6NwHHZRfXy4/mUid/L6W0wA==", + "dependencies": { + "@nextui-org/use-aria-button": "2.0.9", + "@react-aria/utils": "3.24.1", + "@react-stately/toggle": "3.7.4", + "@react-types/button": "3.9.4", + "@react-types/shared": "3.23.1" + }, + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@nextui-org/use-callback-ref": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nextui-org/use-callback-ref/-/use-callback-ref-2.0.5.tgz", + "integrity": "sha512-lcjlV5yaDTiFSv06E5RtQNqy+O6XqH/Q/yz+ka1ZBlZF/FdzEPNRfJ0shN2D7Sh3DdbvV2lySbA2g/0d94geaw==", + "dependencies": { + "@nextui-org/use-safe-layout-effect": "2.0.5" + }, + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@nextui-org/use-clipboard": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nextui-org/use-clipboard/-/use-clipboard-2.0.5.tgz", + "integrity": "sha512-1ExwXM8ENmc/kVDqKoiPGrBP/0B7rZ43iSv2MoWD1Qpc8GHg71Rv7NTIlBDoD/pfUfqkab6x66iKC7AVR8rifA==", + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@nextui-org/use-data-scroll-overflow": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@nextui-org/use-data-scroll-overflow/-/use-data-scroll-overflow-2.1.4.tgz", + "integrity": "sha512-0YqUAe/b9aZftUQOH7sWqBMJHGLyC2Q/ixFyjq8Q1TijrqEyGESGQ2tm0+FHytI04drV+mnsbf6+q2QIKyqGSg==", + "dependencies": { + "@nextui-org/shared-utils": "2.0.5" + }, + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@nextui-org/use-disclosure": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/@nextui-org/use-disclosure/-/use-disclosure-2.0.9.tgz", + "integrity": "sha512-d1Pksmm6zleZAdNraD0s97E+sXHrzI0vZ8tLNzE9yGNOf/VRMBvjpfa9S4Zl7oR+StNbST3JofCqmSHtRNe7hg==", + "dependencies": { + "@nextui-org/use-callback-ref": "2.0.5", + "@react-aria/utils": "3.24.1", + "@react-stately/utils": "3.10.1" + }, + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@nextui-org/use-image": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nextui-org/use-image/-/use-image-2.0.5.tgz", + "integrity": "sha512-FAMyvZS9XSNLqHEmU6xykMgwIFJj/V9/JpTiZAQziz2wqMiUONIBpYpGOlI+pPBNlhCkw62KHm/19vHW49FWhA==", + "dependencies": { + "@nextui-org/use-safe-layout-effect": "2.0.5" + }, + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@nextui-org/use-is-mobile": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@nextui-org/use-is-mobile/-/use-is-mobile-2.0.8.tgz", + "integrity": "sha512-fp6UgfmYTkdri3fKeFUapr0TuJGRTskrTZixh+r1aqTcEWtaeef+Nli5VKRTJb9nqYKkgJDRhC39Z5s/rgq0mA==", + "dependencies": { + "@react-aria/ssr": "3.9.4" + }, + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@nextui-org/use-is-mounted": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nextui-org/use-is-mounted/-/use-is-mounted-2.0.5.tgz", + "integrity": "sha512-gk698Uwmj/XhchBsnI5Ups5uzEXuZvsPK45K6goi2/ADKXSYxHOcSgwoexytqJBb/7tpi+emi2CRTAjAFZDQqA==", + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@nextui-org/use-measure": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@nextui-org/use-measure/-/use-measure-2.0.1.tgz", + "integrity": "sha512-uEtdrdBdFz4Fgbfk2vmQ+rEb+eFa5o4yI90udasvfpaIrMBfrFOlRW5+yn3uXKB8JThET4Gf2on/wlJpo567Dg==", + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@nextui-org/use-pagination": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@nextui-org/use-pagination/-/use-pagination-2.0.7.tgz", + "integrity": "sha512-a05vLp8YSk4nI+LmDUdjjKj2U1/d3Z1ZALUUrjWJVnTUckaiglHGeoYEh8nqcjDXj4sPC4OcK3ZnW+AGUXDGwA==", + "dependencies": { + "@nextui-org/shared-utils": "2.0.5", + "@react-aria/i18n": "3.11.1" + }, + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@nextui-org/use-safe-layout-effect": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nextui-org/use-safe-layout-effect/-/use-safe-layout-effect-2.0.5.tgz", + "integrity": "sha512-YQQlqz82aYxMoEq23jQNG/JBPHF1x3opzyXRHAVxgBEFo9OJqBMZTm23ukpTXm2Ev98T6mpWiTHdfyHJ7IoRog==", + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@nextui-org/use-scroll-position": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@nextui-org/use-scroll-position/-/use-scroll-position-2.0.6.tgz", + "integrity": "sha512-dRwew37XnJOh8d35BuyqzRfnrmKsOUHqi0Owhk0tIGyqifQ/jw65udWpBfa6rwXcd4cKOOqXXHuNGsYTclzc6w==", + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@nextui-org/use-update-effect": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nextui-org/use-update-effect/-/use-update-effect-2.0.5.tgz", + "integrity": "sha512-4r2CXAD598xc2ifMu97kf8V/lj+NDct2oITbxgXeV4ezWaXHy5/26r1iyVnBzRN/VBz3fwHx3hHdftzcYSZxdA==", + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/@nextui-org/user": { + "version": "2.0.31", + "resolved": "https://registry.npmjs.org/@nextui-org/user/-/user-2.0.31.tgz", + "integrity": "sha512-PXWVLB2igKi3MwjVeI5auoK6fhBgT3nizPzabBa95m0/3dg8aex/4oexCRpjef+V5cRD/2z37VHqfelQWqOHjQ==", + "dependencies": { + "@nextui-org/avatar": "2.0.30", + "@nextui-org/react-utils": "2.0.14", + "@nextui-org/shared-utils": "2.0.5", + "@react-aria/focus": "3.17.1", + "@react-aria/utils": "3.24.1" + }, + "peerDependencies": { + "@nextui-org/system": ">=2.0.0", + "@nextui-org/theme": ">=2.1.0", + "react": ">=18", + "react-dom": ">=18" + } + }, + "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==", + "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==", + "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==", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "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/@react-aria/breadcrumbs": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@react-aria/breadcrumbs/-/breadcrumbs-3.5.13.tgz", + "integrity": "sha512-G1Gqf/P6kVdfs94ovwP18fTWuIxadIQgHsXS08JEVcFVYMjb9YjqnEBaohUxD1tq2WldMbYw53ahQblT4NTG+g==", + "dependencies": { + "@react-aria/i18n": "^3.11.1", + "@react-aria/link": "^3.7.1", + "@react-aria/utils": "^3.24.1", + "@react-types/breadcrumbs": "^3.7.5", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/button": { + "version": "3.9.5", + "resolved": "https://registry.npmjs.org/@react-aria/button/-/button-3.9.5.tgz", + "integrity": "sha512-dgcYR6j8WDOMLKuVrtxzx4jIC05cVKDzc+HnPO8lNkBAOfjcuN5tkGRtIjLtqjMvpZHhQT5aDbgFpIaZzxgFIg==", + "dependencies": { + "@react-aria/focus": "^3.17.1", + "@react-aria/interactions": "^3.21.3", + "@react-aria/utils": "^3.24.1", + "@react-stately/toggle": "^3.7.4", + "@react-types/button": "^3.9.4", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/calendar": { + "version": "3.5.8", + "resolved": "https://registry.npmjs.org/@react-aria/calendar/-/calendar-3.5.8.tgz", + "integrity": "sha512-Whlp4CeAA5/ZkzrAHUv73kgIRYjw088eYGSc+cvSOCxfrc/2XkBm9rNrnSBv0DvhJ8AG0Fjz3vYakTmF3BgZBw==", + "dependencies": { + "@internationalized/date": "^3.5.4", + "@react-aria/i18n": "^3.11.1", + "@react-aria/interactions": "^3.21.3", + "@react-aria/live-announcer": "^3.3.4", + "@react-aria/utils": "^3.24.1", + "@react-stately/calendar": "^3.5.1", + "@react-types/button": "^3.9.4", + "@react-types/calendar": "^3.4.6", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/checkbox": { + "version": "3.14.3", + "resolved": "https://registry.npmjs.org/@react-aria/checkbox/-/checkbox-3.14.3.tgz", + "integrity": "sha512-EtBJL6iu0gvrw3A4R7UeVLR6diaVk/mh4kFBc7c8hQjpEJweRr4hmJT3hrNg3MBcTWLxFiMEXPGgWEwXDBygtA==", + "dependencies": { + "@react-aria/form": "^3.0.5", + "@react-aria/interactions": "^3.21.3", + "@react-aria/label": "^3.7.8", + "@react-aria/toggle": "^3.10.4", + "@react-aria/utils": "^3.24.1", + "@react-stately/checkbox": "^3.6.5", + "@react-stately/form": "^3.0.3", + "@react-stately/toggle": "^3.7.4", + "@react-types/checkbox": "^3.8.1", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/combobox": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/@react-aria/combobox/-/combobox-3.9.1.tgz", + "integrity": "sha512-SpK92dCmT8qn8aEcUAihRQrBb5LZUhwIbDExFII8PvUvEFy/PoQHXIo3j1V29WkutDBDpMvBv/6XRCHGXPqrhQ==", + "dependencies": { + "@react-aria/i18n": "^3.11.1", + "@react-aria/listbox": "^3.12.1", + "@react-aria/live-announcer": "^3.3.4", + "@react-aria/menu": "^3.14.1", + "@react-aria/overlays": "^3.22.1", + "@react-aria/selection": "^3.18.1", + "@react-aria/textfield": "^3.14.5", + "@react-aria/utils": "^3.24.1", + "@react-stately/collections": "^3.10.7", + "@react-stately/combobox": "^3.8.4", + "@react-stately/form": "^3.0.3", + "@react-types/button": "^3.9.4", + "@react-types/combobox": "^3.11.1", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/datepicker": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@react-aria/datepicker/-/datepicker-3.10.1.tgz", + "integrity": "sha512-4HZL593nrNMa1GjBmWEN/OTvNS6d3/16G1YJWlqiUlv11ADulSbqBIjMmkgwrJVFcjrgqtXFy+yyrTA/oq94Zw==", + "dependencies": { + "@internationalized/date": "^3.5.4", + "@internationalized/number": "^3.5.3", + "@internationalized/string": "^3.2.3", + "@react-aria/focus": "^3.17.1", + "@react-aria/form": "^3.0.5", + "@react-aria/i18n": "^3.11.1", + "@react-aria/interactions": "^3.21.3", + "@react-aria/label": "^3.7.8", + "@react-aria/spinbutton": "^3.6.5", + "@react-aria/utils": "^3.24.1", + "@react-stately/datepicker": "^3.9.4", + "@react-stately/form": "^3.0.3", + "@react-types/button": "^3.9.4", + "@react-types/calendar": "^3.4.6", + "@react-types/datepicker": "^3.7.4", + "@react-types/dialog": "^3.5.10", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/dialog": { + "version": "3.5.14", + "resolved": "https://registry.npmjs.org/@react-aria/dialog/-/dialog-3.5.14.tgz", + "integrity": "sha512-oqDCjQ8hxe3GStf48XWBf2CliEnxlR9GgSYPHJPUc69WBj68D9rVcCW3kogJnLAnwIyf3FnzbX4wSjvUa88sAQ==", + "dependencies": { + "@react-aria/focus": "^3.17.1", + "@react-aria/overlays": "^3.22.1", + "@react-aria/utils": "^3.24.1", + "@react-types/dialog": "^3.5.10", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/focus": { + "version": "3.17.1", + "resolved": "https://registry.npmjs.org/@react-aria/focus/-/focus-3.17.1.tgz", + "integrity": "sha512-FLTySoSNqX++u0nWZJPPN5etXY0WBxaIe/YuL/GTEeuqUIuC/2bJSaw5hlsM6T2yjy6Y/VAxBcKSdAFUlU6njQ==", + "dependencies": { + "@react-aria/interactions": "^3.21.3", + "@react-aria/utils": "^3.24.1", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/form": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@react-aria/form/-/form-3.0.5.tgz", + "integrity": "sha512-n290jRwrrRXO3fS82MyWR+OKN7yznVesy5Q10IclSTVYHHI3VI53xtAPr/WzNjJR1um8aLhOcDNFKwnNIUUCsQ==", + "dependencies": { + "@react-aria/interactions": "^3.21.3", + "@react-aria/utils": "^3.24.1", + "@react-stately/form": "^3.0.3", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/grid": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/@react-aria/grid/-/grid-3.9.1.tgz", + "integrity": "sha512-fGEZqAEaS8mqzV/II3N4ndoNWegIcbh+L3PmKbXdpKKUP8VgMs/WY5rYl5WAF0f5RoFwXqx3ibDLeR9tKj/bOg==", + "dependencies": { + "@react-aria/focus": "^3.17.1", + "@react-aria/i18n": "^3.11.1", + "@react-aria/interactions": "^3.21.3", + "@react-aria/live-announcer": "^3.3.4", + "@react-aria/selection": "^3.18.1", + "@react-aria/utils": "^3.24.1", + "@react-stately/collections": "^3.10.7", + "@react-stately/grid": "^3.8.7", + "@react-stately/selection": "^3.15.1", + "@react-stately/virtualizer": "^3.7.1", + "@react-types/checkbox": "^3.8.1", + "@react-types/grid": "^3.2.6", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/i18n": { + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/@react-aria/i18n/-/i18n-3.11.1.tgz", + "integrity": "sha512-vuiBHw1kZruNMYeKkTGGnmPyMnM5T+gT8bz97H1FqIq1hQ6OPzmtBZ6W6l6OIMjeHI5oJo4utTwfZl495GALFQ==", + "dependencies": { + "@internationalized/date": "^3.5.4", + "@internationalized/message": "^3.1.4", + "@internationalized/number": "^3.5.3", + "@internationalized/string": "^3.2.3", + "@react-aria/ssr": "^3.9.4", + "@react-aria/utils": "^3.24.1", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/interactions": { + "version": "3.21.3", + "resolved": "https://registry.npmjs.org/@react-aria/interactions/-/interactions-3.21.3.tgz", + "integrity": "sha512-BWIuf4qCs5FreDJ9AguawLVS0lV9UU+sK4CCnbCNNmYqOWY+1+gRXCsnOM32K+oMESBxilAjdHW5n1hsMqYMpA==", + "dependencies": { + "@react-aria/ssr": "^3.9.4", + "@react-aria/utils": "^3.24.1", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/label": { + "version": "3.7.8", + "resolved": "https://registry.npmjs.org/@react-aria/label/-/label-3.7.8.tgz", + "integrity": "sha512-MzgTm5+suPA3KX7Ug6ZBK2NX9cin/RFLsv1BdafJ6CZpmUSpWnGE/yQfYUB7csN7j31OsZrD3/P56eShYWAQfg==", + "dependencies": { + "@react-aria/utils": "^3.24.1", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/link": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@react-aria/link/-/link-3.7.1.tgz", + "integrity": "sha512-a4IaV50P3fXc7DQvEIPYkJJv26JknFbRzFT5MJOMgtzuhyJoQdILEUK6XHYjcSSNCA7uLgzpojArVk5Hz3lCpw==", + "dependencies": { + "@react-aria/focus": "^3.17.1", + "@react-aria/interactions": "^3.21.3", + "@react-aria/utils": "^3.24.1", + "@react-types/link": "^3.5.5", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/listbox": { + "version": "3.12.1", + "resolved": "https://registry.npmjs.org/@react-aria/listbox/-/listbox-3.12.1.tgz", + "integrity": "sha512-7JiUp0NGykbv/HgSpmTY1wqhuf/RmjFxs1HZcNaTv8A+DlzgJYc7yQqFjP3ZA/z5RvJFuuIxggIYmgIFjaRYdA==", + "dependencies": { + "@react-aria/interactions": "^3.21.3", + "@react-aria/label": "^3.7.8", + "@react-aria/selection": "^3.18.1", + "@react-aria/utils": "^3.24.1", + "@react-stately/collections": "^3.10.7", + "@react-stately/list": "^3.10.5", + "@react-types/listbox": "^3.4.9", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/live-announcer": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@react-aria/live-announcer/-/live-announcer-3.3.4.tgz", + "integrity": "sha512-w8lxs35QrRrn6pBNzVfyGOeqWdxeVKf9U6bXIVwhq7rrTqRULL8jqy8RJIMfIs1s8G5FpwWYjyBOjl2g5Cu1iA==", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@react-aria/menu": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/@react-aria/menu/-/menu-3.14.1.tgz", + "integrity": "sha512-BYliRb38uAzq05UOFcD5XkjA5foQoXRbcH3ZufBsc4kvh79BcP1PMW6KsXKGJ7dC/PJWUwCui6QL1kUg8PqMHA==", + "dependencies": { + "@react-aria/focus": "^3.17.1", + "@react-aria/i18n": "^3.11.1", + "@react-aria/interactions": "^3.21.3", + "@react-aria/overlays": "^3.22.1", + "@react-aria/selection": "^3.18.1", + "@react-aria/utils": "^3.24.1", + "@react-stately/collections": "^3.10.7", + "@react-stately/menu": "^3.7.1", + "@react-stately/tree": "^3.8.1", + "@react-types/button": "^3.9.4", + "@react-types/menu": "^3.9.9", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/overlays": { + "version": "3.22.1", + "resolved": "https://registry.npmjs.org/@react-aria/overlays/-/overlays-3.22.1.tgz", + "integrity": "sha512-GHiFMWO4EQ6+j6b5QCnNoOYiyx1Gk8ZiwLzzglCI4q1NY5AG2EAmfU4Z1+Gtrf2S5Y0zHbumC7rs9GnPoGLUYg==", + "dependencies": { + "@react-aria/focus": "^3.17.1", + "@react-aria/i18n": "^3.11.1", + "@react-aria/interactions": "^3.21.3", + "@react-aria/ssr": "^3.9.4", + "@react-aria/utils": "^3.24.1", + "@react-aria/visually-hidden": "^3.8.12", + "@react-stately/overlays": "^3.6.7", + "@react-types/button": "^3.9.4", + "@react-types/overlays": "^3.8.7", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/progress": { + "version": "3.4.13", + "resolved": "https://registry.npmjs.org/@react-aria/progress/-/progress-3.4.13.tgz", + "integrity": "sha512-YBV9bOO5JzKvG8QCI0IAA00o6FczMgIDiK8Q9p5gKorFMatFUdRayxlbIPoYHMi+PguLil0jHgC7eOyaUcrZ0g==", + "dependencies": { + "@react-aria/i18n": "^3.11.1", + "@react-aria/label": "^3.7.8", + "@react-aria/utils": "^3.24.1", + "@react-types/progress": "^3.5.4", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/radio": { + "version": "3.10.4", + "resolved": "https://registry.npmjs.org/@react-aria/radio/-/radio-3.10.4.tgz", + "integrity": "sha512-3fmoMcQtCpgjTwJReFjnvIE/C7zOZeCeWUn4JKDqz9s1ILYsC3Rk5zZ4q66tFn6v+IQnecrKT52wH6+hlVLwTA==", + "dependencies": { + "@react-aria/focus": "^3.17.1", + "@react-aria/form": "^3.0.5", + "@react-aria/i18n": "^3.11.1", + "@react-aria/interactions": "^3.21.3", + "@react-aria/label": "^3.7.8", + "@react-aria/utils": "^3.24.1", + "@react-stately/radio": "^3.10.4", + "@react-types/radio": "^3.8.1", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/selection": { + "version": "3.18.1", + "resolved": "https://registry.npmjs.org/@react-aria/selection/-/selection-3.18.1.tgz", + "integrity": "sha512-GSqN2jX6lh7v+ldqhVjAXDcrWS3N4IsKXxO6L6Ygsye86Q9q9Mq9twWDWWu5IjHD6LoVZLUBCMO+ENGbOkyqeQ==", + "dependencies": { + "@react-aria/focus": "^3.17.1", + "@react-aria/i18n": "^3.11.1", + "@react-aria/interactions": "^3.21.3", + "@react-aria/utils": "^3.24.1", + "@react-stately/selection": "^3.15.1", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/slider": { + "version": "3.7.8", + "resolved": "https://registry.npmjs.org/@react-aria/slider/-/slider-3.7.8.tgz", + "integrity": "sha512-MYvPcM0K8jxEJJicUK2+WxUkBIM/mquBxOTOSSIL3CszA80nXIGVnLlCUnQV3LOUzpWtabbWaZokSPtGgOgQOw==", + "dependencies": { + "@react-aria/focus": "^3.17.1", + "@react-aria/i18n": "^3.11.1", + "@react-aria/interactions": "^3.21.3", + "@react-aria/label": "^3.7.8", + "@react-aria/utils": "^3.24.1", + "@react-stately/slider": "^3.5.4", + "@react-types/shared": "^3.23.1", + "@react-types/slider": "^3.7.3", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/spinbutton": { + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/@react-aria/spinbutton/-/spinbutton-3.6.5.tgz", + "integrity": "sha512-0aACBarF/Xr/7ixzjVBTQ0NBwwwsoGkf5v6AVFVMTC0uYMXHTALvRs+ULHjHMa5e/cX/aPlEvaVT7jfSs+Xy9Q==", + "dependencies": { + "@react-aria/i18n": "^3.11.1", + "@react-aria/live-announcer": "^3.3.4", + "@react-aria/utils": "^3.24.1", + "@react-types/button": "^3.9.4", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/ssr": { + "version": "3.9.4", + "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.4.tgz", + "integrity": "sha512-4jmAigVq409qcJvQyuorsmBR4+9r3+JEC60wC+Y0MZV0HCtTmm8D9guYXlJMdx0SSkgj0hHAyFm/HvPNFofCoQ==", + "dependencies": { + "@swc/helpers": "^0.5.0" + }, + "engines": { + "node": ">= 12" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/switch": { + "version": "3.6.4", + "resolved": "https://registry.npmjs.org/@react-aria/switch/-/switch-3.6.4.tgz", + "integrity": "sha512-2nVqz4ZuJyof47IpGSt3oZRmp+EdS8wzeDYgf42WHQXrx4uEOk1mdLJ20+NnsYhj/2NHZsvXVrjBeKMjlMs+0w==", + "dependencies": { + "@react-aria/toggle": "^3.10.4", + "@react-stately/toggle": "^3.7.4", + "@react-types/switch": "^3.5.3", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/table": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/@react-aria/table/-/table-3.14.1.tgz", + "integrity": "sha512-WaPgQe4zQF5OaluO5rm+Y2nEoFR63vsLd4BT4yjK1uaFhKhDY2Zk+1SCVQvBLLKS4WK9dhP05nrNzT0vp/ZPOw==", + "dependencies": { + "@react-aria/focus": "^3.17.1", + "@react-aria/grid": "^3.9.1", + "@react-aria/i18n": "^3.11.1", + "@react-aria/interactions": "^3.21.3", + "@react-aria/live-announcer": "^3.3.4", + "@react-aria/utils": "^3.24.1", + "@react-aria/visually-hidden": "^3.8.12", + "@react-stately/collections": "^3.10.7", + "@react-stately/flags": "^3.0.3", + "@react-stately/table": "^3.11.8", + "@react-stately/virtualizer": "^3.7.1", + "@react-types/checkbox": "^3.8.1", + "@react-types/grid": "^3.2.6", + "@react-types/shared": "^3.23.1", + "@react-types/table": "^3.9.5", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/tabs": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/@react-aria/tabs/-/tabs-3.9.1.tgz", + "integrity": "sha512-S5v/0sRcOaSXaJYZuuy1ZVzYc7JD4sDyseG1133GjyuNjJOFHgoWMb+b4uxNIJbZxnLgynn/ZDBZSO+qU+fIxw==", + "dependencies": { + "@react-aria/focus": "^3.17.1", + "@react-aria/i18n": "^3.11.1", + "@react-aria/selection": "^3.18.1", + "@react-aria/utils": "^3.24.1", + "@react-stately/tabs": "^3.6.6", + "@react-types/shared": "^3.23.1", + "@react-types/tabs": "^3.3.7", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/textfield": { + "version": "3.14.5", + "resolved": "https://registry.npmjs.org/@react-aria/textfield/-/textfield-3.14.5.tgz", + "integrity": "sha512-hj7H+66BjB1iTKKaFXwSZBZg88YT+wZboEXZ0DNdQB2ytzoz/g045wBItUuNi4ZjXI3P+0AOZznVMYadWBAmiA==", + "dependencies": { + "@react-aria/focus": "^3.17.1", + "@react-aria/form": "^3.0.5", + "@react-aria/label": "^3.7.8", + "@react-aria/utils": "^3.24.1", + "@react-stately/form": "^3.0.3", + "@react-stately/utils": "^3.10.1", + "@react-types/shared": "^3.23.1", + "@react-types/textfield": "^3.9.3", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/toggle": { + "version": "3.10.4", + "resolved": "https://registry.npmjs.org/@react-aria/toggle/-/toggle-3.10.4.tgz", + "integrity": "sha512-bRk+CdB8QzrSyGNjENXiTWxfzYKRw753iwQXsEAU7agPCUdB8cZJyrhbaUoD0rwczzTp2zDbZ9rRbUPdsBE2YQ==", + "dependencies": { + "@react-aria/focus": "^3.17.1", + "@react-aria/interactions": "^3.21.3", + "@react-aria/utils": "^3.24.1", + "@react-stately/toggle": "^3.7.4", + "@react-types/checkbox": "^3.8.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/tooltip": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/@react-aria/tooltip/-/tooltip-3.7.4.tgz", + "integrity": "sha512-+XRx4HlLYqWY3fB8Z60bQi/rbWDIGlFUtXYbtoa1J+EyRWfhpvsYImP8qeeNO/vgjUtDy1j9oKa8p6App9mBMQ==", + "dependencies": { + "@react-aria/focus": "^3.17.1", + "@react-aria/interactions": "^3.21.3", + "@react-aria/utils": "^3.24.1", + "@react-stately/tooltip": "^3.4.9", + "@react-types/shared": "^3.23.1", + "@react-types/tooltip": "^3.4.9", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/utils": { + "version": "3.24.1", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.24.1.tgz", + "integrity": "sha512-O3s9qhPMd6n42x9sKeJ3lhu5V1Tlnzhu6Yk8QOvDuXf7UGuUjXf9mzfHJt1dYzID4l9Fwm8toczBzPM9t0jc8Q==", + "dependencies": { + "@react-aria/ssr": "^3.9.4", + "@react-stately/utils": "^3.10.1", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/visually-hidden": { + "version": "3.8.12", + "resolved": "https://registry.npmjs.org/@react-aria/visually-hidden/-/visually-hidden-3.8.12.tgz", + "integrity": "sha512-Bawm+2Cmw3Xrlr7ARzl2RLtKh0lNUdJ0eNqzWcyx4c0VHUAWtThmH5l+HRqFUGzzutFZVo89SAy40BAbd0gjVw==", + "dependencies": { + "@react-aria/interactions": "^3.21.3", + "@react-aria/utils": "^3.24.1", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-stately/calendar": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@react-stately/calendar/-/calendar-3.5.1.tgz", + "integrity": "sha512-7l7QhqGUJ5AzWHfvZzbTe3J4t72Ht5BmhW4hlVI7flQXtfrmYkVtl3ZdytEZkkHmWGYZRW9b4IQTQGZxhtlElA==", + "dependencies": { + "@internationalized/date": "^3.5.4", + "@react-stately/utils": "^3.10.1", + "@react-types/calendar": "^3.4.6", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-stately/checkbox": { + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/@react-stately/checkbox/-/checkbox-3.6.5.tgz", + "integrity": "sha512-IXV3f9k+LtmfQLE+DKIN41Q5QB/YBLDCB1YVx5PEdRp52S9+EACD5683rjVm8NVRDwjMi2SP6RnFRk7fVb5Azg==", + "dependencies": { + "@react-stately/form": "^3.0.3", + "@react-stately/utils": "^3.10.1", + "@react-types/checkbox": "^3.8.1", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-stately/collections": { + "version": "3.10.7", + "resolved": "https://registry.npmjs.org/@react-stately/collections/-/collections-3.10.7.tgz", + "integrity": "sha512-KRo5O2MWVL8n3aiqb+XR3vP6akmHLhLWYZEmPKjIv0ghQaEebBTrN3wiEjtd6dzllv0QqcWvDLM1LntNfJ2TsA==", + "dependencies": { + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-stately/combobox": { + "version": "3.8.4", + "resolved": "https://registry.npmjs.org/@react-stately/combobox/-/combobox-3.8.4.tgz", + "integrity": "sha512-iLVGvKRRz0TeJXZhZyK783hveHpYA6xovOSdzSD+WGYpiPXo1QrcrNoH3AE0Z2sHtorU+8nc0j58vh5PB+m2AA==", + "dependencies": { + "@react-stately/collections": "^3.10.7", + "@react-stately/form": "^3.0.3", + "@react-stately/list": "^3.10.5", + "@react-stately/overlays": "^3.6.7", + "@react-stately/select": "^3.6.4", + "@react-stately/utils": "^3.10.1", + "@react-types/combobox": "^3.11.1", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-stately/datepicker": { + "version": "3.9.4", + "resolved": "https://registry.npmjs.org/@react-stately/datepicker/-/datepicker-3.9.4.tgz", + "integrity": "sha512-yBdX01jn6gq4NIVvHIqdjBUPo+WN8Bujc4OnPw+ZnfA4jI0eIgq04pfZ84cp1LVXW0IB0VaCu1AlQ/kvtZjfGA==", + "dependencies": { + "@internationalized/date": "^3.5.4", + "@internationalized/string": "^3.2.3", + "@react-stately/form": "^3.0.3", + "@react-stately/overlays": "^3.6.7", + "@react-stately/utils": "^3.10.1", + "@react-types/datepicker": "^3.7.4", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-stately/flags": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@react-stately/flags/-/flags-3.0.3.tgz", + "integrity": "sha512-/ha7XFA0RZTQsbzSPwu3KkbNMgbvuM0GuMTYLTBWpgBrovBNTM+QqI/PfZTdHg8PwCYF4H5Y8gjdSpdulCvJFw==", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@react-stately/form": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@react-stately/form/-/form-3.0.3.tgz", + "integrity": "sha512-92YYBvlHEWUGUpXgIaQ48J50jU9XrxfjYIN8BTvvhBHdD63oWgm8DzQnyT/NIAMzdLnhkg7vP+fjG8LjHeyIAg==", + "dependencies": { + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-stately/grid": { + "version": "3.8.7", + "resolved": "https://registry.npmjs.org/@react-stately/grid/-/grid-3.8.7.tgz", + "integrity": "sha512-he3TXCLAhF5C5z1/G4ySzcwyt7PEiWcVIupxebJQqRyFrNWemSuv+7tolnStmG8maMVIyV3P/3j4eRBbdSlOIg==", + "dependencies": { + "@react-stately/collections": "^3.10.7", + "@react-stately/selection": "^3.15.1", + "@react-types/grid": "^3.2.6", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-stately/list": { + "version": "3.10.5", + "resolved": "https://registry.npmjs.org/@react-stately/list/-/list-3.10.5.tgz", + "integrity": "sha512-fV9plO+6QDHiewsYIhboxcDhF17GO95xepC5ki0bKXo44gr14g/LSo/BMmsaMnV+1BuGdBunB05bO4QOIaigXA==", + "dependencies": { + "@react-stately/collections": "^3.10.7", + "@react-stately/selection": "^3.15.1", + "@react-stately/utils": "^3.10.1", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-stately/menu": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@react-stately/menu/-/menu-3.7.1.tgz", + "integrity": "sha512-mX1w9HHzt+xal1WIT2xGrTQsoLvDwuB2R1Er1MBABs//MsJzccycatcgV/J/28m6tO5M9iuFQQvLV+i1dCtodg==", + "dependencies": { + "@react-stately/overlays": "^3.6.7", + "@react-types/menu": "^3.9.9", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-stately/overlays": { + "version": "3.6.7", + "resolved": "https://registry.npmjs.org/@react-stately/overlays/-/overlays-3.6.7.tgz", + "integrity": "sha512-6zp8v/iNUm6YQap0loaFx6PlvN8C0DgWHNlrlzMtMmNuvjhjR0wYXVaTfNoUZBWj25tlDM81ukXOjpRXg9rLrw==", + "dependencies": { + "@react-stately/utils": "^3.10.1", + "@react-types/overlays": "^3.8.7", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-stately/radio": { + "version": "3.10.4", + "resolved": "https://registry.npmjs.org/@react-stately/radio/-/radio-3.10.4.tgz", + "integrity": "sha512-kCIc7tAl4L7Hu4Wt9l2jaa+MzYmAJm0qmC8G8yPMbExpWbLRu6J8Un80GZu+JxvzgDlqDyrVvyv9zFifwH/NkQ==", + "dependencies": { + "@react-stately/form": "^3.0.3", + "@react-stately/utils": "^3.10.1", + "@react-types/radio": "^3.8.1", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-stately/select": { + "version": "3.6.4", + "resolved": "https://registry.npmjs.org/@react-stately/select/-/select-3.6.4.tgz", + "integrity": "sha512-whZgF1N53D0/dS8tOFdrswB0alsk5Q5620HC3z+5f2Hpi8gwgAZ8TYa+2IcmMYRiT+bxVuvEc/NirU9yPmqGbA==", + "dependencies": { + "@react-stately/form": "^3.0.3", + "@react-stately/list": "^3.10.5", + "@react-stately/overlays": "^3.6.7", + "@react-types/select": "^3.9.4", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-stately/selection": { + "version": "3.15.1", + "resolved": "https://registry.npmjs.org/@react-stately/selection/-/selection-3.15.1.tgz", + "integrity": "sha512-6TQnN9L0UY9w19B7xzb1P6mbUVBtW840Cw1SjgNXCB3NPaCf59SwqClYzoj8O2ZFzMe8F/nUJtfU1NS65/OLlw==", + "dependencies": { + "@react-stately/collections": "^3.10.7", + "@react-stately/utils": "^3.10.1", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-stately/slider": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/@react-stately/slider/-/slider-3.5.4.tgz", + "integrity": "sha512-Jsf7K17dr93lkNKL9ij8HUcoM1sPbq8TvmibD6DhrK9If2lje+OOL8y4n4qreUnfMT56HCAeS9wCO3fg3eMyrw==", + "dependencies": { + "@react-stately/utils": "^3.10.1", + "@react-types/shared": "^3.23.1", + "@react-types/slider": "^3.7.3", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-stately/table": { + "version": "3.11.8", + "resolved": "https://registry.npmjs.org/@react-stately/table/-/table-3.11.8.tgz", + "integrity": "sha512-EdyRW3lT1/kAVDp5FkEIi1BQ7tvmD2YgniGdLuW/l9LADo0T+oxZqruv60qpUS6sQap+59Riaxl91ClDxrJnpg==", + "dependencies": { + "@react-stately/collections": "^3.10.7", + "@react-stately/flags": "^3.0.3", + "@react-stately/grid": "^3.8.7", + "@react-stately/selection": "^3.15.1", + "@react-stately/utils": "^3.10.1", + "@react-types/grid": "^3.2.6", + "@react-types/shared": "^3.23.1", + "@react-types/table": "^3.9.5", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-stately/tabs": { + "version": "3.6.6", + "resolved": "https://registry.npmjs.org/@react-stately/tabs/-/tabs-3.6.6.tgz", + "integrity": "sha512-sOLxorH2uqjAA+v1ppkMCc2YyjgqvSGeBDgtR/lyPSDd4CVMoTExszROX2dqG0c8il9RQvzFuufUtQWMY6PgSA==", + "dependencies": { + "@react-stately/list": "^3.10.5", + "@react-types/shared": "^3.23.1", + "@react-types/tabs": "^3.3.7", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-stately/toggle": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/@react-stately/toggle/-/toggle-3.7.4.tgz", + "integrity": "sha512-CoYFe9WrhLkDP4HGDpJYQKwfiYCRBAeoBQHv+JWl5eyK61S8xSwoHsveYuEZ3bowx71zyCnNAqWRrmNOxJ4CKA==", + "dependencies": { + "@react-stately/utils": "^3.10.1", + "@react-types/checkbox": "^3.8.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-stately/tooltip": { + "version": "3.4.9", + "resolved": "https://registry.npmjs.org/@react-stately/tooltip/-/tooltip-3.4.9.tgz", + "integrity": "sha512-P7CDJsdoKarz32qFwf3VNS01lyC+63gXpDZG31pUu+EO5BeQd4WKN/AH1Beuswpr4GWzxzFc1aXQgERFGVzraA==", + "dependencies": { + "@react-stately/overlays": "^3.6.7", + "@react-types/tooltip": "^3.4.9", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-stately/tree": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@react-stately/tree/-/tree-3.8.1.tgz", + "integrity": "sha512-LOdkkruJWch3W89h4B/bXhfr0t0t1aRfEp+IMrrwdRAl23NaPqwl5ILHs4Xu5XDHqqhg8co73pHrJwUyiTWEjw==", + "dependencies": { + "@react-stately/collections": "^3.10.7", + "@react-stately/selection": "^3.15.1", + "@react-stately/utils": "^3.10.1", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-stately/utils": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@react-stately/utils/-/utils-3.10.1.tgz", + "integrity": "sha512-VS/EHRyicef25zDZcM/ClpzYMC5i2YGN6uegOeQawmgfGjb02yaCX0F0zR69Pod9m2Hr3wunTbtpgVXvYbZItg==", + "dependencies": { + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-stately/virtualizer": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@react-stately/virtualizer/-/virtualizer-3.7.1.tgz", + "integrity": "sha512-voHgE6EQ+oZaLv6u2umKxakvIKNkCQuUihqKACTjdslp7SJh4Mvs3oLBI0hf0JOh+rCcFIKDvQtFwy1fXFRYBA==", + "dependencies": { + "@react-aria/utils": "^3.24.1", + "@react-types/shared": "^3.23.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-types/accordion": { + "version": "3.0.0-alpha.21", + "resolved": "https://registry.npmjs.org/@react-types/accordion/-/accordion-3.0.0-alpha.21.tgz", + "integrity": "sha512-cbE06jH/ZoI+1898xd7ocQ/A/Rtkz8wTJAVOYgc8VRY1SYNQ/XZTGH5T6dD6aERAmiDwL/kjD7xhsE80DyaEKA==", + "dependencies": { + "@react-types/shared": "^3.23.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-types/breadcrumbs": { + "version": "3.7.5", + "resolved": "https://registry.npmjs.org/@react-types/breadcrumbs/-/breadcrumbs-3.7.5.tgz", + "integrity": "sha512-lV9IDYsMiu2TgdMIjEmsOE0YWwjb3jhUNK1DCZZfq6uWuiHLgyx2EncazJBUWSjHJ4ta32j7xTuXch+8Ai6u/A==", + "dependencies": { + "@react-types/link": "^3.5.5", + "@react-types/shared": "^3.23.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-types/button": { + "version": "3.9.4", + "resolved": "https://registry.npmjs.org/@react-types/button/-/button-3.9.4.tgz", + "integrity": "sha512-raeQBJUxBp0axNF74TXB8/H50GY8Q3eV6cEKMbZFP1+Dzr09Ngv0tJBeW0ewAxAguNH5DRoMUAUGIXtSXskVdA==", + "dependencies": { + "@react-types/shared": "^3.23.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-types/calendar": { + "version": "3.4.6", + "resolved": "https://registry.npmjs.org/@react-types/calendar/-/calendar-3.4.6.tgz", + "integrity": "sha512-WSntZPwtvsIYWvBQRAPvuCn55UTJBZroTvX0vQvWykJRQnPAI20G1hMQ3dNsnAL+gLZUYxBXn66vphmjUuSYew==", + "dependencies": { + "@internationalized/date": "^3.5.4", + "@react-types/shared": "^3.23.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-types/checkbox": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@react-types/checkbox/-/checkbox-3.8.1.tgz", + "integrity": "sha512-5/oVByPw4MbR/8QSdHCaalmyWC71H/QGgd4aduTJSaNi825o+v/hsN2/CH7Fq9atkLKsC8fvKD00Bj2VGaKriQ==", + "dependencies": { + "@react-types/shared": "^3.23.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-types/combobox": { + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/@react-types/combobox/-/combobox-3.11.1.tgz", + "integrity": "sha512-UNc3OHt5cUt5gCTHqhQIqhaWwKCpaNciD8R7eQazmHiA9fq8ROlV+7l3gdNgdhJbTf5Bu/V5ISnN7Y1xwL3zqQ==", + "dependencies": { + "@react-types/shared": "^3.23.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-types/datepicker": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/@react-types/datepicker/-/datepicker-3.7.4.tgz", + "integrity": "sha512-ZfvgscvNzBJpYyVWg3nstJtA/VlWLwErwSkd1ivZYam859N30w8yH+4qoYLa6FzWLCFlrsRHyvtxlEM7lUAt5A==", + "dependencies": { + "@internationalized/date": "^3.5.4", + "@react-types/calendar": "^3.4.6", + "@react-types/overlays": "^3.8.7", + "@react-types/shared": "^3.23.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-types/dialog": { + "version": "3.5.10", + "resolved": "https://registry.npmjs.org/@react-types/dialog/-/dialog-3.5.10.tgz", + "integrity": "sha512-S9ga+edOLNLZw7/zVOnZdT5T40etpzUYBXEKdFPbxyPYnERvRxJAsC1/ASuBU9fQAXMRgLZzADWV+wJoGS/X9g==", + "dependencies": { + "@react-types/overlays": "^3.8.7", + "@react-types/shared": "^3.23.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-types/grid": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@react-types/grid/-/grid-3.2.6.tgz", + "integrity": "sha512-XfHenL2jEBUYrhKiPdeM24mbLRXUn79wVzzMhrNYh24nBwhsPPpxF+gjFddT3Cy8dt6tRInfT6pMEu9nsXwaHw==", + "dependencies": { + "@react-types/shared": "^3.23.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-types/link": { + "version": "3.5.5", + "resolved": "https://registry.npmjs.org/@react-types/link/-/link-3.5.5.tgz", + "integrity": "sha512-G6P5WagHDR87npN7sEuC5IIgL1GsoY4WFWKO4734i2CXRYx24G9P0Su3AX4GA3qpspz8sK1AWkaCzBMmvnunfw==", + "dependencies": { + "@react-types/shared": "^3.23.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-types/listbox": { + "version": "3.4.9", + "resolved": "https://registry.npmjs.org/@react-types/listbox/-/listbox-3.4.9.tgz", + "integrity": "sha512-S5G+WmNKUIOPZxZ4svWwWQupP3C6LmVfnf8QQmPDvwYXGzVc0WovkqUWyhhjJirFDswTXRCO9p0yaTHHIlkdwQ==", + "dependencies": { + "@react-types/shared": "^3.23.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-types/menu": { + "version": "3.9.9", + "resolved": "https://registry.npmjs.org/@react-types/menu/-/menu-3.9.9.tgz", + "integrity": "sha512-FamUaPVs1Fxr4KOMI0YcR2rYZHoN7ypGtgiEiJ11v/tEPjPPGgeKDxii0McCrdOkjheatLN1yd2jmMwYj6hTDg==", + "dependencies": { + "@react-types/overlays": "^3.8.7", + "@react-types/shared": "^3.23.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-types/overlays": { + "version": "3.8.7", + "resolved": "https://registry.npmjs.org/@react-types/overlays/-/overlays-3.8.7.tgz", + "integrity": "sha512-zCOYvI4at2DkhVpviIClJ7bRrLXYhSg3Z3v9xymuPH3mkiuuP/dm8mUCtkyY4UhVeUTHmrQh1bzaOP00A+SSQA==", + "dependencies": { + "@react-types/shared": "^3.23.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-types/progress": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/@react-types/progress/-/progress-3.5.4.tgz", + "integrity": "sha512-JNc246sTjasPyx5Dp7/s0rp3Bz4qlu4LrZTulZlxWyb53WgBNL7axc26CCi+I20rWL9+c7JjhrRxnLl/1cLN5g==", + "dependencies": { + "@react-types/shared": "^3.23.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-types/radio": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@react-types/radio/-/radio-3.8.1.tgz", + "integrity": "sha512-bK0gio/qj1+0Ldu/3k/s9BaOZvnnRgvFtL3u5ky479+aLG5qf1CmYed3SKz8ErZ70JkpuCSrSwSCFf0t1IHovw==", + "dependencies": { + "@react-types/shared": "^3.23.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-types/select": { + "version": "3.9.4", + "resolved": "https://registry.npmjs.org/@react-types/select/-/select-3.9.4.tgz", + "integrity": "sha512-xI7dnOW2st91fPPcv6hdtrTdcfetYiqZuuVPZ5TRobY7Q10/Zqqe/KqtOw1zFKUj9xqNJe4Ov3xP5GSdcO60Eg==", + "dependencies": { + "@react-types/shared": "^3.23.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-types/shared": { + "version": "3.23.1", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.23.1.tgz", + "integrity": "sha512-5d+3HbFDxGZjhbMBeFHRQhexMFt4pUce3okyRtUVKbbedQFUrtXSBg9VszgF2RTeQDKDkMCIQDtz5ccP/Lk1gw==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-types/slider": { + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/@react-types/slider/-/slider-3.7.3.tgz", + "integrity": "sha512-F8qFQaD2mqug2D0XeWMmjGBikiwbdERFlhFzdvNGbypPLz3AZICBKp1ZLPWdl0DMuy03G/jy6Gl4mDobl7RT2g==", + "dependencies": { + "@react-types/shared": "^3.23.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-types/switch": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/@react-types/switch/-/switch-3.5.3.tgz", + "integrity": "sha512-Nb6+J5MrPaFa8ZNFKGMzAsen/NNzl5UG/BbC65SLGPy7O0VDa/sUpn7dcu8V2xRpRwwIN/Oso4v63bt2sgdkgA==", + "dependencies": { + "@react-types/shared": "^3.23.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-types/table": { + "version": "3.9.5", + "resolved": "https://registry.npmjs.org/@react-types/table/-/table-3.9.5.tgz", + "integrity": "sha512-fgM2j9F/UR4Anmd28CueghCgBwOZoCVyN8fjaIFPd2MN4gCwUUfANwxLav65gZk4BpwUXGoQdsW+X50L3555mg==", + "dependencies": { + "@react-types/grid": "^3.2.6", + "@react-types/shared": "^3.23.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-types/tabs": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/@react-types/tabs/-/tabs-3.3.7.tgz", + "integrity": "sha512-ZdLe5xOcFX6+/ni45Dl2jO0jFATpTnoSqj6kLIS/BYv8oh0n817OjJkLf+DS3CLfNjApJWrHqAk34xNh6nRnEg==", + "dependencies": { + "@react-types/shared": "^3.23.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-types/textfield": { + "version": "3.9.3", + "resolved": "https://registry.npmjs.org/@react-types/textfield/-/textfield-3.9.3.tgz", + "integrity": "sha512-DoAY6cYOL0pJhgNGI1Rosni7g72GAt4OVr2ltEx2S9ARmFZ0DBvdhA9lL2nywcnKMf27PEJcKMXzXc10qaHsJw==", + "dependencies": { + "@react-types/shared": "^3.23.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-types/tooltip": { + "version": "3.4.9", + "resolved": "https://registry.npmjs.org/@react-types/tooltip/-/tooltip-3.4.9.tgz", + "integrity": "sha512-wZ+uF1+Zc43qG+cOJzioBmLUNjRa7ApdcT0LI1VvaYvH5GdfjzUJOorLX9V/vAci0XMJ50UZ+qsh79aUlw2yqg==", + "dependencies": { + "@react-types/overlays": "^3.8.7", + "@react-types/shared": "^3.23.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@rushstack/eslint-patch": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.10.3.tgz", + "integrity": "sha512-qC/xYId4NMebE6w/V33Fh9gWxLgURiNYgVNObbJl2LZv0GUUItCcCqC5axQSwRaAgaxl2mELq1rMzlswaQ0Zxg==", + "dev": true + }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==" + }, + "node_modules/@swc/helpers": { + "version": "0.5.11", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.11.tgz", + "integrity": "sha512-YNlnKRWF2sVojTpIyzwou9XoTNbzbzONwRhOoniEioF1AtaitTvVZblaQRrAzChWQ1bLYyYSWzM18y4WwgzJ+A==", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@tanstack/react-table": { + "version": "8.19.3", + "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.19.3.tgz", + "integrity": "sha512-MtgPZc4y+cCRtU16y1vh1myuyZ2OdkWgMEBzyjYsoMWMicKZGZvcDnub3Zwb6XF2pj9iRMvm1SO1n57lS0vXLw==", + "dependencies": { + "@tanstack/table-core": "8.19.3" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@tanstack/table-core": { + "version": "8.19.3", + "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.19.3.tgz", + "integrity": "sha512-IqREj9ADoml9zCAouIG/5kCGoyIxPFdqdyoxis9FisXFi5vT+iYfEfLosq4xkU/iDbMcEuAj+X8dWRLvKYDNoQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@ts-morph/common": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.19.0.tgz", + "integrity": "sha512-Unz/WHmd4pGax91rdIKWi51wnVUW11QttMEPpBiBgIewnc9UQIX7UDLxr5vRlqeByXCwhkF6VabSsI0raWcyAQ==", + "dependencies": { + "fast-glob": "^3.2.12", + "minimatch": "^7.4.3", + "mkdirp": "^2.1.6", + "path-browserify": "^1.0.1" + } + }, + "node_modules/@ts-morph/common/node_modules/minimatch": { + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.6.tgz", + "integrity": "sha512-sBz8G/YjVniEz6lKPNpKxXwazJe4c19fEfV2GDMX6AjFz+MX9uDWIZW8XreVhkFW3fkIdTv/gxWr/Kks5FFAVw==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, + "node_modules/@types/lodash": { + "version": "4.17.6", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.6.tgz", + "integrity": "sha512-OpXEVoCKSS3lQqjx9GGGOapBeuW5eUboYHRlHP9urXPX25IKZ6AnP5ZRxtVf63iieUbsHxLn8NQ5Nlftc6yzAA==" + }, + "node_modules/@types/lodash.debounce": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/lodash.debounce/-/lodash.debounce-4.0.9.tgz", + "integrity": "sha512-Ma5JcgTREwpLRwMM+XwBR7DaWe96nC38uCBDFKZWbNKD+osjVzdpnUSwBcqCptrp16sSOLBAUb50Car5I0TCsQ==", + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/node": { + "version": "20.5.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.7.tgz", + "integrity": "sha512-dP7f3LdZIysZnmvP3ANJYTSwg+wLLl8p7RqniVlV7j+oXSXAbt9h0WIBFmJy5inWZoX9wZN6eXx+YXd9Rh3RBA==", + "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", + "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.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.15.0.tgz", + "integrity": "sha512-uiNHpyjZtFrLwLDpHnzaDlP3Tt6sGMqTCiqmxaN4n4RP0EfYZDODJyddiFDF44Hjwxr5xAcaYxVKm9QKQFJFLA==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "7.15.0", + "@typescript-eslint/type-utils": "7.15.0", + "@typescript-eslint/utils": "7.15.0", + "@typescript-eslint/visitor-keys": "7.15.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.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.15.0.tgz", + "integrity": "sha512-k9fYuQNnypLFcqORNClRykkGOMOj+pV6V91R4GO/l1FDGwpqmSwoOQrOHo3cGaH63e+D3ZiCAOsuS/D2c99j/A==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "7.15.0", + "@typescript-eslint/types": "7.15.0", + "@typescript-eslint/typescript-estree": "7.15.0", + "@typescript-eslint/visitor-keys": "7.15.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.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.15.0.tgz", + "integrity": "sha512-Q/1yrF/XbxOTvttNVPihxh1b9fxamjEoz2Os/Pe38OHwxC24CyCqXxGTOdpb4lt6HYtqw9HetA/Rf6gDGaMPlw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.15.0", + "@typescript-eslint/visitor-keys": "7.15.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.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.15.0.tgz", + "integrity": "sha512-SkgriaeV6PDvpA6253PDVep0qCqgbO1IOBiycjnXsszNTVQe5flN5wR5jiczoEoDEnAqYFSFFc9al9BSGVltkg==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "7.15.0", + "@typescript-eslint/utils": "7.15.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.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.15.0.tgz", + "integrity": "sha512-aV1+B1+ySXbQH0pLK0rx66I3IkiZNidYobyfn0WFsdGhSXw+P3YOqeTq5GED458SfB24tg+ux3S+9g118hjlTw==", + "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.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.15.0.tgz", + "integrity": "sha512-gjyB/rHAopL/XxfmYThQbXbzRMGhZzGw6KpcMbfe8Q3nNQKStpxnUKeXb0KiN/fFDR42Z43szs6rY7eHk0zdGQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.15.0", + "@typescript-eslint/visitor-keys": "7.15.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.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.15.0.tgz", + "integrity": "sha512-hfDMDqaqOqsUVGiEPSMLR/AjTSCsmJwjpKkYQRo1FNbmW4tBwBspYDwO9eh7sKSTwMQgBw9/T4DHudPaqshRWA==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "7.15.0", + "@typescript-eslint/types": "7.15.0", + "@typescript-eslint/typescript-estree": "7.15.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.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.15.0.tgz", + "integrity": "sha512-Hqgy/ETgpt2L5xueA/zHHIl4fJI2O4XUE9l4+OIfbJIRSnTJb/QscncdqqZzofQegIJugRIF57OJea1khw2SDw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.15.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/acorn": { + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "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/add": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/add/-/add-2.0.6.tgz", + "integrity": "sha512-j5QzrmsokwWWp6kUcJQySpbG+xfOBqqKnup3OIk1pz+kB/80SLorZ9V8zHFLO92Lcd+hbvq8bT+zOGoPkmBV0Q==" + }, + "node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "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/alert": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/alert/-/alert-6.0.2.tgz", + "integrity": "sha512-Oi8u2HRNN6mzpjgKGii2Uuf9iOhyfbeUAHH/5MwnVmC8DS9GrEBjZBFpoavkNj+ZKnBr/Lqx+6YKLDKrggKfPA==", + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, + "node_modules/ansi-escapes": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-6.2.1.tgz", + "integrity": "sha512-4nJ3yixlEthEJ9Rk4vPcdBRkZvQZlYyu8j4/Mqz5sgIkddmEnH2Yj2ZrnP9S3tQOvSNRUIgVNF/1yPpRAGNRig==", + "dev": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "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==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==" + }, + "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==" + }, + "node_modules/aria-query": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", + "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", + "dev": true, + "dependencies": { + "deep-equal": "^2.0.5" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", + "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-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/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz", + "integrity": "sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", + "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", + "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.toreversed": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/array.prototype.toreversed/-/array.prototype.toreversed-1.1.2.tgz", + "integrity": "sha512-wwDCoT4Ck4Cz7sLtgUmzR5UV3YF5mFHUlbChCzZBQZ+0m2cl/DH3tKgvphv1nKgFsJ48oCSg6p91q2Vm0I/ZMA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", + "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", + "is-shared-array-buffer": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ast-types": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.16.1.tgz", + "integrity": "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/ast-types-flow": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", + "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", + "dev": true + }, + "node_modules/autoprefixer": { + "version": "10.4.19", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz", + "integrity": "sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "browserslist": "^4.23.0", + "caniuse-lite": "^1.0.30001599", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.0", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axe-core": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.9.1.tgz", + "integrity": "sha512-QbUdXJVTpvUTHU7871ppZkdOLBeGUKBQWHkHrvN2V9IQWGMt61zf3B45BtzjxEJzYuj0JBjBZP/hmYS/R9pmAw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/axobject-query": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.1.1.tgz", + "integrity": "sha512-goKlv8DZrK9hUh975fnHzhNIO4jUnFCfv/dszV5VwUGDFjI6vQ2VwoyjYjYNEbBE8AH87TduWP5uyDR1D+Iteg==", + "dev": true, + "dependencies": { + "deep-equal": "^2.0.5" + } + }, + "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==" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "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/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bl": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", + "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==", + "dependencies": { + "buffer": "^6.0.3", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "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==", + "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==", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.23.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.1.tgz", + "integrity": "sha512-TUfofFo/KsK/bWZ9TWQ5O26tsWW4Uhmt8IYklbnUa70udB6P2wA7w7o4PY4muaEPBQaAX+CEnmmIA41NVHtPVw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001629", + "electron-to-chromium": "^1.4.796", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.16" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "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": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "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==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001640", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001640.tgz", + "integrity": "sha512-lA4VMpW0PSUrFnkmVuEKBUovSWKhj7puyCg8StBChgu298N1AtuF1sKWEvfDuimSEDbhlb/KqPKC3fs1HbuQUA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "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/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/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==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/class-variance-authority": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.0.tgz", + "integrity": "sha512-jFI8IQw4hczaL4ALINxqLEXQbWcNjoSkloa4IaufXCJr6QawJyw7tuRysRsrE8w2p/4gGaxKIt/hX3qz/IbD1A==", + "dependencies": { + "clsx": "2.0.0" + }, + "funding": { + "url": "https://joebell.co.uk" + } + }, + "node_modules/class-variance-authority/node_modules/clsx": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", + "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==", + "engines": { + "node": ">=6" + } + }, + "node_modules/cli-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", + "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", + "dependencies": { + "restore-cursor": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", + "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", + "dev": true, + "dependencies": { + "slice-ansi": "^5.0.0", + "string-width": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/cli-truncate/node_modules/emoji-regex": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz", + "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==", + "dev": true + }, + "node_modules/cli-truncate/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==" + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "engines": { + "node": ">=0.8" + } + }, + "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/code-block-writer": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-12.0.0.tgz", + "integrity": "sha512-q4dMFMlXtKR3XNBHyMHt/3pwYNA69EDk00lloMOaaUMKPUXBw6lpXtbu3MMVG6/uOihGnRDOlkyqsONEUj60+w==" + }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, + "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==", + "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==" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/color2k": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/color2k/-/color2k-2.0.3.tgz", + "integrity": "sha512-zW190nQTIoXcGCaU08DvVNFTmQhUpnJfVuAKfWqUQkflXKpaDdpaYoM0iluLS9lgJNHyBF58KKA2FBEwkD7wog==" + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/compute-scroll-into-view": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.1.0.tgz", + "integrity": "sha512-rj8l8pD4bJ1nx+dAkMhV1xB5RuZEyVysfxJqB1pRchh1KVvwOv9b7CGB8ZfjTImVv2oF+sYMUkMZq6Na5Ftmbg==" + }, + "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/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" + }, + "node_modules/cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "dependencies": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/cross-spawn": { + "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" + }, + "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==", + "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==", + "devOptional": true + }, + "node_modules/damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", + "dev": true + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "engines": { + "node": ">= 12" + } + }, + "node_modules/data-view-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", + "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", + "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", + "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/date-fns": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz", + "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, + "node_modules/debug": { + "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" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-equal": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz", + "integrity": "sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.5", + "es-get-iterator": "^1.1.3", + "get-intrinsic": "^1.2.2", + "is-arguments": "^1.1.1", + "is-array-buffer": "^3.0.2", + "is-date-object": "^1.0.5", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "isarray": "^2.0.5", + "object-is": "^1.1.5", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.5.1", + "side-channel": "^1.0.4", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/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/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==" + }, + "node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "engines": { + "node": ">=0.3.1" + } + }, + "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/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==" + }, + "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/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" + }, + "node_modules/electron-to-chromium": { + "version": "1.4.818", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.818.tgz", + "integrity": "sha512-eGvIk2V0dGImV9gWLq8fDfTTsCAeMDwZqEPMr+jMInxZdnp9Us8UpovYpRCf9NQ7VOFgrN2doNSgvISbsbNpxA==" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + }, + "node_modules/enhanced-resolve": { + "version": "5.17.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.0.tgz", + "integrity": "sha512-dwDPwZL0dmye8Txp2gzFmA6sxALaSvdRDjPH0viLcKrtlOL3tw62nWWweVD1SdILDTJrbrL6tdWVN58Wo6U3eA==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "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==", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/error-ex/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==" + }, + "node_modules/es-abstract": { + "version": "1.23.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", + "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "data-view-buffer": "^1.0.1", + "data-view-byte-length": "^1.0.1", + "data-view-byte-offset": "^1.0.0", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.0.3", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "hasown": "^2.0.2", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.1", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.3", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.13", + "is-weakref": "^1.0.2", + "object-inspect": "^1.13.1", + "object-keys": "^1.1.1", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.2", + "safe-array-concat": "^1.1.2", + "safe-regex-test": "^1.0.3", + "string.prototype.trim": "^1.2.9", + "string.prototype.trimend": "^1.0.8", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.2", + "typed-array-byte-length": "^1.0.1", + "typed-array-byte-offset": "^1.0.2", + "typed-array-length": "^1.0.6", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.15" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-get-iterator": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", + "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "has-symbols": "^1.0.3", + "is-arguments": "^1.1.1", + "is-map": "^2.0.2", + "is-set": "^2.0.2", + "is-string": "^1.0.7", + "isarray": "^2.0.5", + "stop-iteration-iterator": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.0.19", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.19.tgz", + "integrity": "sha512-zoMwbCcH5hwUkKJkT8kDIBZSz9I6mVG//+lDCinLCGov4+r7NIy0ld8o03M0cJxl2spVf6ESYVS6/gpIfq1FFw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.3", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "globalthis": "^1.0.3", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.7", + "iterator.prototype": "^1.1.2", + "safe-array-concat": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", + "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", + "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "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-config-next": { + "version": "14.2.1", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-14.2.1.tgz", + "integrity": "sha512-BgD0kPCWMlqoItRf3xe9fG0MqwObKfVch+f2ccwDpZiCJA8ghkz2wrASH+bI6nLZzGcOJOpMm1v1Q1euhfpt4Q==", + "dev": true, + "dependencies": { + "@next/eslint-plugin-next": "14.2.1", + "@rushstack/eslint-patch": "^1.3.3", + "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || 7.0.0 - 7.2.0", + "eslint-import-resolver-node": "^0.3.6", + "eslint-import-resolver-typescript": "^3.5.2", + "eslint-plugin-import": "^2.28.1", + "eslint-plugin-jsx-a11y": "^6.7.1", + "eslint-plugin-react": "^7.33.2", + "eslint-plugin-react-hooks": "^4.5.0 || 5.0.0-canary-7118f5dd7-20230705" + }, + "peerDependencies": { + "eslint": "^7.23.0 || ^8.0.0", + "typescript": ">=3.3.1" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-config-next/node_modules/@next/eslint-plugin-next": { + "version": "14.2.1", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-14.2.1.tgz", + "integrity": "sha512-Fp+mthEBjkn8r9qd6o4JgxKp0IDEzW0VYHD8ZC05xS5/lFNwHKuOdr2kVhWG7BQCO9L6eeepshM1Wbs2T+LgSg==", + "dev": true, + "dependencies": { + "glob": "10.3.10" + } + }, + "node_modules/eslint-config-next/node_modules/@typescript-eslint/parser": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.2.0.tgz", + "integrity": "sha512-5FKsVcHTk6TafQKQbuIVkXq58Fnbkd2wDL4LB7AURN7RUOu1utVP+G8+6u3ZhEroW3DF6hyo3ZEXxgKgp4KeCg==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "7.2.0", + "@typescript-eslint/types": "7.2.0", + "@typescript-eslint/typescript-estree": "7.2.0", + "@typescript-eslint/visitor-keys": "7.2.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-config-next/node_modules/@typescript-eslint/scope-manager": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.2.0.tgz", + "integrity": "sha512-Qh976RbQM/fYtjx9hs4XkayYujB/aPwglw2choHmf3zBjB4qOywWSdt9+KLRdHubGcoSwBnXUH2sR3hkyaERRg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.2.0", + "@typescript-eslint/visitor-keys": "7.2.0" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/eslint-config-next/node_modules/@typescript-eslint/types": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.2.0.tgz", + "integrity": "sha512-XFtUHPI/abFhm4cbCDc5Ykc8npOKBSJePY3a3s+lwumt7XWJuzP5cZcfZ610MIPHjQjNsOLlYK8ASPaNG8UiyA==", + "dev": true, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/eslint-config-next/node_modules/@typescript-eslint/typescript-estree": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.2.0.tgz", + "integrity": "sha512-cyxS5WQQCoBwSakpMrvMXuMDEbhOo9bNHHrNcEWis6XHx6KF518tkF1wBvKIn/tpq5ZpUYK7Bdklu8qY0MsFIA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.2.0", + "@typescript-eslint/visitor-keys": "7.2.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "9.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-config-next/node_modules/@typescript-eslint/visitor-keys": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.2.0.tgz", + "integrity": "sha512-c6EIQRHhcpl6+tO8EMR+kjkkV+ugUNXOmeASA1rlzkd8EPIriavpWoiEz1HR/VLhbVIdhqnV6E7JZm00cBDx2A==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.2.0", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/eslint-config-next/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-import-resolver-typescript": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.6.1.tgz", + "integrity": "sha512-xgdptdoi5W3niYeuQxKmzVDTATvLYqhpwmykwsh7f6HIOStGWEIL9iqZgQDF9u9OEzrRwR8no5q2VT+bjAujTg==", + "dev": true, + "dependencies": { + "debug": "^4.3.4", + "enhanced-resolve": "^5.12.0", + "eslint-module-utils": "^2.7.4", + "fast-glob": "^3.3.1", + "get-tsconfig": "^4.5.0", + "is-core-module": "^2.11.0", + "is-glob": "^4.0.3" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts/projects/eslint-import-resolver-ts" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.1.tgz", + "integrity": "sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==", + "dev": true, + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-es": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz", + "integrity": "sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==", + "dev": true, + "dependencies": { + "eslint-utils": "^2.0.0", + "regexpp": "^3.0.0" + }, + "engines": { + "node": ">=8.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=4.19.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", + "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.7", + "array.prototype.findlastindex": "^1.2.3", + "array.prototype.flat": "^1.3.2", + "array.prototype.flatmap": "^1.3.2", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.8.0", + "hasown": "^2.0.0", + "is-core-module": "^2.13.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.7", + "object.groupby": "^1.0.1", + "object.values": "^1.1.7", + "semver": "^6.3.1", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + } + }, + "node_modules/eslint-plugin-import/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-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/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-plugin-import/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-jsx-a11y": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.9.0.tgz", + "integrity": "sha512-nOFOCaJG2pYqORjK19lqPqxMO/JpvdCZdPtNdxY3kvom3jTvkAbOvQvD8wuD0G8BYR0IGAGYDlzqWJOh/ybn2g==", + "dev": true, + "dependencies": { + "aria-query": "~5.1.3", + "array-includes": "^3.1.8", + "array.prototype.flatmap": "^1.3.2", + "ast-types-flow": "^0.0.8", + "axe-core": "^4.9.1", + "axobject-query": "~3.1.1", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", + "es-iterator-helpers": "^1.0.19", + "hasown": "^2.0.2", + "jsx-ast-utils": "^3.3.5", + "language-tags": "^1.0.9", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "safe-regex-test": "^1.0.3", + "string.prototype.includes": "^2.0.0" + }, + "engines": { + "node": ">=4.0" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" + } + }, + "node_modules/eslint-plugin-jsx-a11y/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-plugin-jsx-a11y/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-plugin-node": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", + "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", + "dev": true, + "dependencies": { + "eslint-plugin-es": "^3.0.0", + "eslint-utils": "^2.0.0", + "ignore": "^5.1.1", + "minimatch": "^3.0.4", + "resolve": "^1.10.1", + "semver": "^6.1.0" + }, + "engines": { + "node": ">=8.10.0" + }, + "peerDependencies": { + "eslint": ">=5.16.0" + } + }, + "node_modules/eslint-plugin-node/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-plugin-node/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-plugin-node/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz", + "integrity": "sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==", + "dev": true, + "dependencies": { + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.8.6" + }, + "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": { + "version": "7.34.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.34.3.tgz", + "integrity": "sha512-aoW4MV891jkUulwDApQbPYTVZmeuSyFrudpbTAQuj5Fv8VL+o6df2xIGpw8B0hPjAaih1/Fb0om9grCdyFYemA==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.2", + "array.prototype.toreversed": "^1.1.2", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.0.19", + "estraverse": "^5.3.0", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.8", + "object.fromentries": "^2.0.8", + "object.hasown": "^1.1.4", + "object.values": "^1.2.0", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.11" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz", + "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==", + "dev": true, + "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/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-plugin-react/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-react/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-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/eslint-plugin-react/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-security": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-security/-/eslint-plugin-security-3.0.1.tgz", + "integrity": "sha512-XjVGBhtDZJfyuhIxnQ/WMm385RbX3DBu7H1J7HNNhmB2tnGxMeqVSnYv79oAj992ayvIBZghsymwkYFS6cGH4Q==", + "dev": true, + "dependencies": { + "safe-regex": "^2.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-plugin-simple-import-sort": { + "version": "12.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-simple-import-sort/-/eslint-plugin-simple-import-sort-12.1.1.tgz", + "integrity": "sha512-6nuzu4xwQtE3332Uz0to+TxDQYRLTKRESSc2hefVT48Zc8JthmN23Gx9lnYhu0FtkRSL1oxny3kJ2aveVhmOVA==", + "dev": true, + "peerDependencies": { + "eslint": ">=5.0.0" + } + }, + "node_modules/eslint-plugin-unused-imports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-3.2.0.tgz", + "integrity": "sha512-6uXyn6xdINEpxE1MtDjxQsyXB37lfyO2yKGVVgtD7WEWQGORSOZjgrD6hBhvGv4/SO+TOlS+UnC6JppRqbuwGQ==", + "dev": true, + "dependencies": { + "eslint-rule-composer": "^0.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "6 - 7", + "eslint": "8" + }, + "peerDependenciesMeta": { + "@typescript-eslint/eslint-plugin": { + "optional": true + } + } + }, + "node_modules/eslint-rule-composer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz", + "integrity": "sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg==", + "dev": true, + "engines": { + "node": ">=4.0.0" + } + }, + "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-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "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/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "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": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "dev": true + }, + "node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "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-glob": { + "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" + }, + "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==", + "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==", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "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==", + "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": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "bin": { + "flat": "cli.js" + } + }, + "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/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/foreground-child": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz", + "integrity": "sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "dev": true, + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/framer-motion": { + "version": "11.1.9", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.1.9.tgz", + "integrity": "sha512-flECDIPV4QDNcOrDafVFiIazp8X01HFpzc01eDKJsdNH/wrATcYydJSH9JbPWMS8UD5lZlw+J1sK8LG2kICgqw==", + "dependencies": { + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0", + "react-dom": "^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/fs-extra": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "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/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", + "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.2.0.tgz", + "integrity": "sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-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/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", + "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-tsconfig": { + "version": "4.7.5", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.5.tgz", + "integrity": "sha512-ZCuZCnlqNzjb4QprAzXKdpp/gh6KTxSJuw3IBsPnV/7fV4NxC9ckB+vPTt8w7fJA0TaSD7c55BR47JD6MEDyDw==", + "dev": true, + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob": { + "version": "10.3.10", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", + "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.5", + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", + "path-scurry": "^1.10.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "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==", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "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/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, + "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-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/https-proxy-agent": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-6.2.1.tgz", + "integrity": "sha512-ONsE3+yfZF2caH5+bJlcddtWqNI3Gvs5A38+ngvljxaBiRXRswym2c7yf8UAeFpRFKjFNHIFEHqR/OLAWJzyiA==", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/husky": { + "version": "9.0.11", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.0.11.tgz", + "integrity": "sha512-AB6lFlbwwyIqMdHYhwPe+kjOC3Oc5P3nThEoW/AaO2BX3vJDjWPFxYLxokUZOo6RNX20He3AaT8sESs9NJcmEw==", + "dev": true, + "bin": { + "husky": "bin.mjs" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "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/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==", + "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==" + }, + "node_modules/internal-slot": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.0", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/intl-messageformat": { + "version": "10.5.14", + "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.5.14.tgz", + "integrity": "sha512-IjC6sI0X7YRjjyVH9aUgdftcmZK7WXdHeil4KwbjDnRWjnVitKpAx3rr6t6di1joFp5188VqKcobOPA6mCLG/w==", + "dependencies": { + "@formatjs/ecma402-abstract": "2.0.0", + "@formatjs/fast-memoize": "2.2.0", + "@formatjs/icu-messageformat-parser": "2.7.8", + "tslib": "^2.4.0" + } + }, + "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-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", + "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + }, + "node_modules/is-async-function": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz", + "integrity": "sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", + "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", + "dev": true, + "dependencies": { + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz", + "integrity": "sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-interactive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", + "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", + "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", + "dev": true, + "dependencies": { + "which-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-unicode-supported": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", + "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.3.tgz", + "integrity": "sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "node_modules/iterator.prototype": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.2.tgz", + "integrity": "sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==", + "dev": true, + "dependencies": { + "define-properties": "^1.2.1", + "get-intrinsic": "^1.2.1", + "has-symbols": "^1.0.3", + "reflect.getprototypeof": "^1.0.4", + "set-function-name": "^2.0.1" + } + }, + "node_modules/jackspeak": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", + "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jiti": { + "version": "1.21.6", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz", + "integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "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==", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "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==" + }, + "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/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/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/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "engines": { + "node": ">=6" + } + }, + "node_modules/language-subtag-registry": { + "version": "0.3.23", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", + "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==", + "dev": true + }, + "node_modules/language-tags": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", + "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", + "dev": true, + "dependencies": { + "language-subtag-registry": "^0.3.20" + }, + "engines": { + "node": ">=0.10" + } + }, + "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/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "engines": { + "node": ">=10" + } + }, + "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==" + }, + "node_modules/lint-staged": { + "version": "15.2.7", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.7.tgz", + "integrity": "sha512-+FdVbbCZ+yoh7E/RosSdqKJyUM2OEjTciH0TFNkawKgvFp1zbGlEC39RADg+xKBG1R4mhoH2j85myBQZ5wR+lw==", + "dev": true, + "dependencies": { + "chalk": "~5.3.0", + "commander": "~12.1.0", + "debug": "~4.3.4", + "execa": "~8.0.1", + "lilconfig": "~3.1.1", + "listr2": "~8.2.1", + "micromatch": "~4.0.7", + "pidtree": "~0.6.0", + "string-argv": "~0.3.2", + "yaml": "~2.4.2" + }, + "bin": { + "lint-staged": "bin/lint-staged.js" + }, + "engines": { + "node": ">=18.12.0" + }, + "funding": { + "url": "https://opencollective.com/lint-staged" + } + }, + "node_modules/lint-staged/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/lint-staged/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/lint-staged/node_modules/lilconfig": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz", + "integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/listr2": { + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.2.3.tgz", + "integrity": "sha512-Lllokma2mtoniUOS94CcOErHWAug5iu7HOmDrvWgpw8jyQH2fomgB+7lZS4HWZxytUuQwkGOwe49FvwVaA85Xw==", + "dev": true, + "dependencies": { + "cli-truncate": "^4.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.0.0", + "rfdc": "^1.4.1", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/listr2/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/listr2/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/listr2/node_modules/emoji-regex": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz", + "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==", + "dev": true + }, + "node_modules/listr2/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/listr2/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/listr2/node_modules/wrap-ansi": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", + "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "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._reinterpolate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", + "integrity": "sha512-xYHt68QRoYGjeeM/XOE1uJtvXQAgvszfBhjV4yvsQH0u2i9I6cI6c6/eG4Hh3UAOVn0y/xAXwmTzEay49Q//HA==" + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" + }, + "node_modules/lodash.foreach": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-4.5.0.tgz", + "integrity": "sha512-aEXTF4d+m05rVOAUG3z4vZZ4xVexLKZGF0lIxuHZ1Hplpk/3B6Z1+/ICICYRLm7c41Z2xiejbkCkJoTlypoXhQ==" + }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==" + }, + "node_modules/lodash.kebabcase": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz", + "integrity": "sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==" + }, + "node_modules/lodash.mapkeys": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.mapkeys/-/lodash.mapkeys-4.6.0.tgz", + "integrity": "sha512-0Al+hxpYvONWtg+ZqHpa/GaVzxuN3V7Xeo2p+bY06EaK/n+Y9R7nBePPN2o1LxmL0TWQSwP8LYZ008/hc9JzhA==" + }, + "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/lodash.omit": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.omit/-/lodash.omit-4.5.0.tgz", + "integrity": "sha512-XeqSp49hNGmlkj2EJlfrQFIzQ6lXdNro9sddtQzcJY8QaoC2GO0DT7xaIokHeyM+mIT0mPMlPvkYzg2xCuHdZg==" + }, + "node_modules/lodash.template": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz", + "integrity": "sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==", + "dependencies": { + "lodash._reinterpolate": "^3.0.0", + "lodash.templatesettings": "^4.0.0" + } + }, + "node_modules/lodash.templatesettings": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz", + "integrity": "sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==", + "dependencies": { + "lodash._reinterpolate": "^3.0.0" + } + }, + "node_modules/log-symbols": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-5.1.0.tgz", + "integrity": "sha512-l0x2DvrW294C9uDCoQe1VSU4gf529FkSZ6leBl4TiqZH/e+0R7hSfHQBNut2mNygDgHwvYHfFLn6Oxb3VWj2rA==", + "dependencies": { + "chalk": "^5.0.0", + "is-unicode-supported": "^1.1.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/log-update": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.0.0.tgz", + "integrity": "sha512-niTvB4gqvtof056rRIrTZvjNYE4rCUzO6X/X+kYjd7WFxXeJ0NwEFnRxX6ehkvv3jTwrXnNdtAak5XYZuIyPFw==", + "dev": true, + "dependencies": { + "ansi-escapes": "^6.2.0", + "cli-cursor": "^4.0.0", + "slice-ansi": "^7.0.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/log-update/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-update/node_modules/emoji-regex": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz", + "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==", + "dev": true + }, + "node_modules/log-update/node_modules/is-fullwidth-code-point": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz", + "integrity": "sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==", + "dev": true, + "dependencies": { + "get-east-asian-width": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/slice-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.0.tgz", + "integrity": "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/wrap-ansi": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", + "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "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/lru-cache": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.0.tgz", + "integrity": "sha512-bfJaPTuEiTYBu+ulDaeQ0F+uLmlfFkMgXj4cbwfuMSjgObGMzb55FMMbDvbRU0fAHZ4sLGkz2mKwcMg8Dvm8Ww==", + "engines": { + "node": ">=18" + } + }, + "node_modules/lucide-react": { + "version": "0.417.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.417.0.tgz", + "integrity": "sha512-F/MDUHDter8YMZ7JKQpW/5/+v38tdaoShKX3e+opYsqfCnaHwn+5zz3+lBrMDFMNtSsvxtNpchLIaMpEfsi/4w==", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "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==", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mkdirp": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-2.1.6.tgz", + "integrity": "sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "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==" + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "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/next": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/next/-/next-14.2.3.tgz", + "integrity": "sha512-dowFkFTR8v79NPJO4QsBUtxv0g9BrS/phluVpMAt2ku7H+cbcBJlopXjkWlwxrk/xGqMemr7JkGPGemPrLLX7A==", + "dependencies": { + "@next/env": "14.2.3", + "@swc/helpers": "0.5.5", + "busboy": "1.6.0", + "caniuse-lite": "^1.0.30001579", + "graceful-fs": "^4.2.11", + "postcss": "8.4.31", + "styled-jsx": "5.1.1" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": ">=18.17.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "14.2.3", + "@next/swc-darwin-x64": "14.2.3", + "@next/swc-linux-arm64-gnu": "14.2.3", + "@next/swc-linux-arm64-musl": "14.2.3", + "@next/swc-linux-x64-gnu": "14.2.3", + "@next/swc-linux-x64-musl": "14.2.3", + "@next/swc-win32-arm64-msvc": "14.2.3", + "@next/swc-win32-ia32-msvc": "14.2.3", + "@next/swc-win32-x64-msvc": "14.2.3" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.41.2", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/next-themes": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.2.1.tgz", + "integrity": "sha512-B+AKNfYNIzh0vqQQKqQItTS8evEouKD7H5Hj3kmuPERwddR2TxvDSFZuTj6T7Jfn1oyeUyJMydPl1Bkxkh0W7A==", + "peerDependencies": { + "next": "*", + "react": "*", + "react-dom": "*" + } + }, + "node_modules/next/node_modules/@swc/helpers": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.5.tgz", + "integrity": "sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==", + "dependencies": { + "@swc/counter": "^0.1.3", + "tslib": "^2.4.0" + } + }, + "node_modules/next/node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "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.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/node-releases": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/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/object-inspect": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-is": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", + "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.8.tgz", + "integrity": "sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.hasown": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.4.tgz", + "integrity": "sha512-FZ9LZt9/RHzGySlBARE3VF+gE26TxR38SdmqOqliuTnl9wrKulaQs+4dee1V+Io8VfxqzAfHu6YuRgUy8OHoTg==", + "dev": true, + "dependencies": { + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.values": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", + "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/ora": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-6.3.1.tgz", + "integrity": "sha512-ERAyNnZOfqM+Ao3RAvIXkYh5joP220yf59gVe2X/cI6SiCxIdi4c9HZKZD8R6q/RDXEje1THBju6iExiSsgJaQ==", + "dependencies": { + "chalk": "^5.0.0", + "cli-cursor": "^4.0.0", + "cli-spinners": "^2.6.1", + "is-interactive": "^2.0.0", + "is-unicode-supported": "^1.1.0", + "log-symbols": "^5.1.0", + "stdin-discarder": "^0.1.0", + "strip-ansi": "^7.0.1", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ora/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/ora/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "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==", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "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-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==" + }, + "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==", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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==", + "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==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pidtree": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", + "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", + "dev": true, + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "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-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "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==", + "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-load-config": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", + "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "lilconfig": "^3.0.0", + "yaml": "^2.3.4" + }, + "engines": { + "node": ">= 14" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-load-config/node_modules/lilconfig": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz", + "integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "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==", + "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-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==", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" + }, + "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/prettier": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.2.tgz", + "integrity": "sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==", + "dev": true, + "peer": 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/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "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==", + "dev": true, + "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==", + "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==", + "dev": true + }, + "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-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/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/recast": { + "version": "0.23.9", + "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.9.tgz", + "integrity": "sha512-Hx/BGIbwj+Des3+xy5uAtAbdCyqK9y9wbBcDFDYanLS9JnMqf7OeF87HQwUimE87OEc72mr6tkKUKMBBL+hF9Q==", + "dependencies": { + "ast-types": "^0.16.1", + "esprima": "~4.0.0", + "source-map": "~0.6.1", + "tiny-invariant": "^1.3.3", + "tslib": "^2.0.1" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz", + "integrity": "sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.1", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "globalthis": "^1.0.3", + "which-builtin-type": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, + "node_modules/regexp-tree": { + "version": "0.1.27", + "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.27.tgz", + "integrity": "sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==", + "dev": true, + "bin": { + "regexp-tree": "bin/regexp-tree" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", + "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "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==", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/restore-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", + "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/restore-cursor/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true + }, + "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/rimraf/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/rimraf/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/rimraf/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/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==", + "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/safe-array-concat": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", + "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "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/safe-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-2.1.1.tgz", + "integrity": "sha512-rx+x8AMzKb5Q5lQ95Zoi6ZbJqwCLkqi3XuJXp5P3rT8OEc6sZCJG5AE5dU3lsgRr/F4Bs31jSlVN+j5KrsGu9A==", + "dev": true, + "dependencies": { + "regexp-tree": "~0.1.1" + } + }, + "node_modules/safe-regex-test": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", + "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-regex": "^1.1.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/scroll-into-view-if-needed": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.0.10.tgz", + "integrity": "sha512-t44QCeDKAPf1mtQH3fYpWz8IM/DyvHLjs8wUvvwMYxk5moOqCzrMSxK6HQVD0QVmVjXFavoFIPRVrMuJPKAvtg==", + "dependencies": { + "compute-scroll-into-view": "^3.0.2" + } + }, + "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/server-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/server-only/-/server-only-0.0.1.tgz", + "integrity": "sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==" + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/shadcn-ui": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/shadcn-ui/-/shadcn-ui-0.8.0.tgz", + "integrity": "sha512-avqRgjJ6PIQQXdfvoCAWQpyLTLk6oHhtU5DQKmLeYcgu1ZIsgMqA9MKWAkr0HpEdCAenCCZvFbvJ2C2m5ZXRiA==", + "dependencies": { + "@antfu/ni": "^0.21.4", + "@babel/core": "^7.22.1", + "@babel/parser": "^7.22.6", + "@babel/plugin-transform-typescript": "^7.22.5", + "chalk": "5.2.0", + "commander": "^10.0.0", + "cosmiconfig": "^8.1.3", + "diff": "^5.1.0", + "execa": "^7.0.0", + "fast-glob": "^3.3.2", + "fs-extra": "^11.1.0", + "https-proxy-agent": "^6.2.0", + "lodash.template": "^4.5.0", + "node-fetch": "^3.3.0", + "ora": "^6.1.2", + "prompts": "^2.4.2", + "recast": "^0.23.2", + "ts-morph": "^18.0.0", + "tsconfig-paths": "^4.2.0", + "zod": "^3.20.2" + }, + "bin": { + "shadcn-ui": "dist/index.js" + } + }, + "node_modules/shadcn-ui/node_modules/chalk": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.2.0.tgz", + "integrity": "sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/shadcn-ui/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "engines": { + "node": ">=14" + } + }, + "node_modules/shadcn-ui/node_modules/execa": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz", + "integrity": "sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.1", + "human-signals": "^4.3.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^3.0.7", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": "^14.18.0 || ^16.14.0 || >=18.0.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/shadcn-ui/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/shadcn-ui/node_modules/human-signals": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz", + "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==", + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/shadcn-ui/node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/shadcn-ui/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "node_modules/shadcn-ui/node_modules/tsconfig-paths": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", + "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", + "dependencies": { + "json5": "^2.2.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "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==", + "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==", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==" + }, + "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/slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "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", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stdin-discarder": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.1.0.tgz", + "integrity": "sha512-xhV7w8S+bUwlPTb4bAOUQhv8/cSS5offJuX8GQGq32ONF0ZtDWKfkdomM3HMRA+LhX6um/FZ0COqlwsjD53LeQ==", + "dependencies": { + "bl": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stop-iteration-iterator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", + "integrity": "sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==", + "dev": true, + "dependencies": { + "internal-slot": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-argv": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", + "dev": true, + "engines": { + "node": ">=0.6.19" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/string.prototype.includes": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.0.tgz", + "integrity": "sha512-E34CkBgyeqNDcrbU76cDjL5JLcVrtSdYq0MEh/B10r17pRP4ciHLwTgnuLV8Ay6cgEMLkcBkFCKyFZ43YldYzg==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz", + "integrity": "sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.7", + "regexp.prototype.flags": "^1.5.2", + "set-function-name": "^2.0.2", + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", + "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", + "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "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" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/styled-jsx": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz", + "integrity": "sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==", + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/sucrase": { + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", + "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "^10.3.10", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "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/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==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/synckit": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.8.tgz", + "integrity": "sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==", + "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/tailwind-merge": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.4.0.tgz", + "integrity": "sha512-49AwoOQNKdqKPd9CViyH5wJoSKsCDjUlzL8DxuGp3P1FsGY36NJDAa18jLZcaHAUUuTj+JB8IAo8zWgBNvBF7A==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwind-variants": { + "version": "0.1.20", + "resolved": "https://registry.npmjs.org/tailwind-variants/-/tailwind-variants-0.1.20.tgz", + "integrity": "sha512-AMh7x313t/V+eTySKB0Dal08RHY7ggYK0MSn/ad8wKWOrDUIzyiWNayRUm2PIJ4VRkvRnfNuyRuKbLV3EN+ewQ==", + "dependencies": { + "tailwind-merge": "^1.14.0" + }, + "engines": { + "node": ">=16.x", + "pnpm": ">=7.x" + }, + "peerDependencies": { + "tailwindcss": "*" + } + }, + "node_modules/tailwind-variants/node_modules/tailwind-merge": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-1.14.0.tgz", + "integrity": "sha512-3mFKyCo/MBcgyOTlrY8T7odzZFx+w+qKSMAmdFzRvqBfLlSigU6TZnlFHK0lkMwj9Bj8OYU+9yW9lmGuS0QEnQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.3.tgz", + "integrity": "sha512-U7sxQk/n397Bmx4JHbJx/iSOOv5G+II3f1kpLpY2QeUv5DcPdcTsYLlusZfq1NthHS1c1cZoyFmmkex1rzke0A==", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.5.3", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.0", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.0", + "lilconfig": "^2.1.0", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.23", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.1", + "postcss-nested": "^6.0.1", + "postcss-selector-parser": "^6.0.11", + "resolve": "^1.22.2", + "sucrase": "^3.32.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tailwindcss-animate": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz", + "integrity": "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==", + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders" + } + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "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/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "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-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==", + "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", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "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/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==" + }, + "node_modules/ts-morph": { + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-18.0.0.tgz", + "integrity": "sha512-Kg5u0mk19PIIe4islUI/HWRvm9bC1lHejK4S0oh1zaZ77TMZAEmQC0sHQYiu2RgCQFZKXz1fMVi/7nOOeirznA==", + "dependencies": { + "@ts-morph/common": "~0.19.0", + "code-block-writer": "^12.0.0" + } + }, + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.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": "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/typed-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", + "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", + "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", + "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", + "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", + "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", + "devOptional": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", + "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.2", + "picocolors": "^1.0.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "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/usehooks-ts": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/usehooks-ts/-/usehooks-ts-3.1.0.tgz", + "integrity": "sha512-bBIa7yUyPhE1BCc0GmR96VU/15l/9gP1Ch5mYdLcFBaFGQsdmXkvjV0TtOqW1yUd6VjIwDunm+flSciCQXujiw==", + "dev": true, + "dependencies": { + "lodash.debounce": "^4.0.8" + }, + "engines": { + "node": ">=16.15.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18" + } + }, + "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==" + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/which": { + "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" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.1.3.tgz", + "integrity": "sha512-YmjsSMDBYsM1CaFiayOVT06+KJeXf0o5M/CAd4o1lTadFAtacTUM49zoYxr/oroopFDfhvN6iEcBxUyc3gvKmw==", + "dev": true, + "dependencies": { + "function.prototype.name": "^1.1.5", + "has-tostringtag": "^1.0.0", + "is-async-function": "^2.0.0", + "is-date-object": "^1.0.5", + "is-finalizationregistry": "^1.0.2", + "is-generator-function": "^1.0.10", + "is-regex": "^1.1.4", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", + "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "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/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + }, + "node_modules/yaml": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.5.tgz", + "integrity": "sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg==", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, + "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" + } + }, + "node_modules/zod": { + "version": "3.23.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} From 5b31ce848433c2a385b5e617575e435ee26216d2 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Thu, 1 Aug 2024 13:38:51 +0200 Subject: [PATCH 079/411] feat: add Dockerfile and .dockerignore --- .dockerignore | 15 ++++++++++++ Dockerfile | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 .dockerignore create mode 100644 Dockerfile diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000000..df076390d2 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,15 @@ +# Include any files or directories that you don't want to be copied to your +# container here (e.g., local build artifacts, temporary files, etc.). +# +# For more help, visit the .dockerignore file reference guide at +# https://docs.docker.com/go/build-context-dockerignore/ + +Dockerfile +.dockerignore +node_modules +npm-debug.log +README.md +.next +!.next/static +!.next/standalone +.git diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000..3d605ad3e1 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,65 @@ +FROM node:20-alpine AS base + +# Install dependencies only when needed +FROM base AS deps +# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed. +RUN apk add --no-cache libc6-compat +WORKDIR /app + +# Install dependencies based on the preferred package manager +COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./ +RUN \ + if [ -f yarn.lock ]; then yarn --frozen-lockfile; \ + elif [ -f package-lock.json ]; then npm install; \ + else echo "Lockfile not found." && exit 1; \ + fi + + +# Rebuild the source code only when needed +FROM base AS builder +WORKDIR /app +COPY --from=deps /app/node_modules ./node_modules +COPY . . + +# Next.js collects completely anonymous telemetry data about general usage. +# Learn more here: https://nextjs.org/telemetry +# Uncomment the following line in case you want to disable telemetry during the build. +# ENV NEXT_TELEMETRY_DISABLED 1 + +RUN \ + if [ -f yarn.lock ]; then yarn run build; \ + elif [ -f package-lock.json ]; then npm run build; \ + else echo "Lockfile not found." && exit 1; \ + fi + +# Production image, copy all the files and run next +FROM base AS runner +WORKDIR /app + +ENV NODE_ENV production +# Uncomment the following line in case you want to disable telemetry during runtime. +# ENV NEXT_TELEMETRY_DISABLED 1 + +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nextjs + +COPY --from=builder /app/public ./public + +# Set the correct permission for prerender cache +RUN mkdir .next +RUN chown nextjs:nodejs .next + +# Automatically leverage output traces to reduce image size +# https://nextjs.org/docs/advanced-features/output-file-tracing +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static + +USER nextjs + +EXPOSE 3000 + +ENV PORT 3000 + +# server.js is created by next build from the standalone output +# https://nextjs.org/docs/pages/api-reference/next-config-js/output +CMD HOSTNAME="0.0.0.0" node server.js From 8fc9204946a99e1eff27e9a68142b1c9f5d137aa Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Thu, 1 Aug 2024 15:04:30 +0200 Subject: [PATCH 080/411] chore: optimize the Dockerfile --- Dockerfile | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Dockerfile b/Dockerfile index 3d605ad3e1..ac9d96ef35 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,16 +1,17 @@ FROM node:20-alpine AS base +LABEL maintainer="https://github.com/prowler-cloud" # Install dependencies only when needed FROM base AS deps # Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed. +#hadolint ignore=DL3018 RUN apk add --no-cache libc6-compat WORKDIR /app # Install dependencies based on the preferred package manager COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./ RUN \ - if [ -f yarn.lock ]; then yarn --frozen-lockfile; \ - elif [ -f package-lock.json ]; then npm install; \ + if [ -f package-lock.json ]; then npm install; \ else echo "Lockfile not found." && exit 1; \ fi @@ -27,8 +28,7 @@ COPY . . # ENV NEXT_TELEMETRY_DISABLED 1 RUN \ - if [ -f yarn.lock ]; then yarn run build; \ - elif [ -f package-lock.json ]; then npm run build; \ + if [ -f package-lock.json ]; then npm run build; \ else echo "Lockfile not found." && exit 1; \ fi @@ -36,18 +36,18 @@ RUN \ FROM base AS runner WORKDIR /app -ENV NODE_ENV production +ENV NODE_ENV development # Uncomment the following line in case you want to disable telemetry during runtime. # ENV NEXT_TELEMETRY_DISABLED 1 -RUN addgroup --system --gid 1001 nodejs -RUN adduser --system --uid 1001 nextjs +RUN addgroup --system --gid 1001 nodejs &&\ +adduser --system --uid 1001 nextjs COPY --from=builder /app/public ./public # Set the correct permission for prerender cache -RUN mkdir .next -RUN chown nextjs:nodejs .next +RUN mkdir .next &&\ +chown nextjs:nodejs .next # Automatically leverage output traces to reduce image size # https://nextjs.org/docs/advanced-features/output-file-tracing @@ -59,7 +59,7 @@ USER nextjs EXPOSE 3000 ENV PORT 3000 - +ENV HOSTNAME "0.0.0.0" # server.js is created by next build from the standalone output # https://nextjs.org/docs/pages/api-reference/next-config-js/output -CMD HOSTNAME="0.0.0.0" node server.js +CMD ["node", "server.js"] From de55eeb1837285e961075e0080fa185f4563f367 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Thu, 1 Aug 2024 15:44:42 +0200 Subject: [PATCH 081/411] chore: disable telemetry during the build --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index ac9d96ef35..a9df928b18 100644 --- a/Dockerfile +++ b/Dockerfile @@ -25,7 +25,7 @@ COPY . . # Next.js collects completely anonymous telemetry data about general usage. # Learn more here: https://nextjs.org/telemetry # Uncomment the following line in case you want to disable telemetry during the build. -# ENV NEXT_TELEMETRY_DISABLED 1 +ENV NEXT_TELEMETRY_DISABLED 1 RUN \ if [ -f package-lock.json ]; then npm run build; \ From a3555af684cba4437f3aee8427e36b4166108aca Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Thu, 1 Aug 2024 15:48:11 +0200 Subject: [PATCH 082/411] chore: disable telemetry during the runtime --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index a9df928b18..e007a41d07 100644 --- a/Dockerfile +++ b/Dockerfile @@ -38,7 +38,7 @@ WORKDIR /app ENV NODE_ENV development # Uncomment the following line in case you want to disable telemetry during runtime. -# ENV NEXT_TELEMETRY_DISABLED 1 +ENV NEXT_TELEMETRY_DISABLED 1 RUN addgroup --system --gid 1001 nodejs &&\ adduser --system --uid 1001 nextjs From 25ec271a7f23036cfd0b1cbabe85ee007a82b016 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Thu, 1 Aug 2024 15:52:06 +0200 Subject: [PATCH 083/411] chore: optimize the Dockerfile --- Dockerfile | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index e007a41d07..d35c218542 100644 --- a/Dockerfile +++ b/Dockerfile @@ -45,10 +45,6 @@ adduser --system --uid 1001 nextjs COPY --from=builder /app/public ./public -# Set the correct permission for prerender cache -RUN mkdir .next &&\ -chown nextjs:nodejs .next - # Automatically leverage output traces to reduce image size # https://nextjs.org/docs/advanced-features/output-file-tracing COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ From acfbdc6405453c7f0b19d692e756625b610160ba Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Thu, 1 Aug 2024 15:56:11 +0200 Subject: [PATCH 084/411] chore: optimize the Dockerfile, remove all related with .nextjs folder --- Dockerfile | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Dockerfile b/Dockerfile index d35c218542..6e3ef8b488 100644 --- a/Dockerfile +++ b/Dockerfile @@ -45,11 +45,6 @@ adduser --system --uid 1001 nextjs COPY --from=builder /app/public ./public -# Automatically leverage output traces to reduce image size -# https://nextjs.org/docs/advanced-features/output-file-tracing -COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ -COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static - USER nextjs EXPOSE 3000 From a9462da78e0f90fd14837004d19c2c249b51744f Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Thu, 1 Aug 2024 16:39:31 +0200 Subject: [PATCH 085/411] fix: SWR NextJS compiler --- package-lock.json | 120 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) diff --git a/package-lock.json b/package-lock.json index 860d71df9d..16e75f1e08 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10597,6 +10597,126 @@ "funding": { "url": "https://github.com/sponsors/colinhacks" } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.3.tgz", + "integrity": "sha512-6adp7waE6P1TYFSXpY366xwsOnEXM+y1kgRpjSRVI2CBDOcbRjsJ67Z6EgKIqWIue52d2q/Mx8g9MszARj8IEA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.3.tgz", + "integrity": "sha512-cuzCE/1G0ZSnTAHJPUT1rPgQx1w5tzSX7POXSLaS7w2nIUJUD+e25QoXD/hMfxbsT9rslEXugWypJMILBj/QsA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.3.tgz", + "integrity": "sha512-0D4/oMM2Y9Ta3nGuCcQN8jjJjmDPYpHX9OJzqk42NZGJocU2MqhBq5tWkJrUQOQY9N+In9xOdymzapM09GeiZw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.3.tgz", + "integrity": "sha512-ENPiNnBNDInBLyUU5ii8PMQh+4XLr4pG51tOp6aJ9xqFQ2iRI6IH0Ds2yJkAzNV1CfyagcyzPfROMViS2wOZ9w==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.3.tgz", + "integrity": "sha512-BTAbq0LnCbF5MtoM7I/9UeUu/8ZBY0i8SFjUMCbPDOLv+un67e2JgyN4pmgfXBwy/I+RHu8q+k+MCkDN6P9ViQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.3.tgz", + "integrity": "sha512-AEHIw/dhAMLNFJFJIJIyOFDzrzI5bAjI9J26gbO5xhAKHYTZ9Or04BesFPXiAYXDNdrwTP2dQceYA4dL1geu8A==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-ia32-msvc": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.3.tgz", + "integrity": "sha512-vga40n1q6aYb0CLrM+eEmisfKCR45ixQYXuBXxOOmmoV8sYST9k7E3US32FsY+CkkF7NtzdcebiFT4CHuMSyZw==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.3.tgz", + "integrity": "sha512-Q1/zm43RWynxrO7lW4ehciQVj+5ePBhOK+/K2P7pLFX3JaJ/IZVC69SHidrmZSOkqz7ECIOhhy7XhAFG4JYyHA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } } } } From 2bfa37ca2e11b294a5ab113c31d4972d0614131e Mon Sep 17 00:00:00 2001 From: Sophia Dao Date: Thu, 1 Aug 2024 17:41:05 -0500 Subject: [PATCH 086/411] feat(findings): WIP - add in initial data table setup, add in some hardcoded value for display purposes, future skeleton loader --- app/(prowler)/findings/page.tsx | 54 ++++++++++-- components/index.ts | 2 + .../providers/table/ColumnsFindings.tsx | 86 +++++++++++++++++++ .../providers/table/SkeletonTableFindings.tsx | 65 ++++++++++++++ types/components.ts | 12 +++ 5 files changed, 214 insertions(+), 5 deletions(-) create mode 100644 components/providers/table/ColumnsFindings.tsx create mode 100644 components/providers/table/SkeletonTableFindings.tsx diff --git a/app/(prowler)/findings/page.tsx b/app/(prowler)/findings/page.tsx index 373833f41e..2595124a58 100644 --- a/app/(prowler)/findings/page.tsx +++ b/app/(prowler)/findings/page.tsx @@ -1,13 +1,57 @@ -import React from "react"; +import { Spacer } from "@nextui-org/react"; +import React, { Suspense } from "react"; -import { Header } from "@/components"; +import { + ColumnsFindings, + DataTable, + Header, + SkeletonTableFindings, +} from "@/components"; -export default function Findings() { +export default async function Findings() { return ( <>
- -

Hi hi from Findings page

+ +
+ + }> + + +
); } + +const SSRDataTable = async () => { + return ( + + ); +}; diff --git a/components/index.ts b/components/index.ts index 4cad0baacf..98f77ea0f2 100644 --- a/components/index.ts +++ b/components/index.ts @@ -1,8 +1,10 @@ export * from "./providers/DateWithTime"; export * from "./providers/ProviderInfo"; export * from "./providers/ScanStatus"; +export * from "./providers/table/ColumnsFindings"; export * from "./providers/table/ColumnsProviders"; export * from "./providers/table/DataTable"; +export * from "./providers/table/SkeletonTableFindings"; export * from "./providers/table/SkeletonTableProvider"; export * from "./ui/alert/Alert"; export * from "./ui/header/Header"; diff --git a/components/providers/table/ColumnsFindings.tsx b/components/providers/table/ColumnsFindings.tsx new file mode 100644 index 0000000000..90860b260e --- /dev/null +++ b/components/providers/table/ColumnsFindings.tsx @@ -0,0 +1,86 @@ +"use client"; + +import { + Button, + Dropdown, + DropdownItem, + DropdownMenu, + DropdownTrigger, +} from "@nextui-org/react"; +import { ColumnDef } from "@tanstack/react-table"; + +import { VerticalDotsIcon } from "@/components/icons"; +import { FindingsProps } from "@/types"; + +const getFindingsAttributes = (row: { original: FindingsProps }) => { + return row.original.attributes; +}; + +export const ColumnsFindings: ColumnDef[] = [ + { + accessorKey: "checkTitle", + header: "Check", + cell: ({ row }) => { + const { CheckTitle } = getFindingsAttributes(row); + return

{CheckTitle}

; + }, + }, + { + accessorKey: "severity", + header: "Severity", + cell: ({ row }) => { + const { severity } = getFindingsAttributes(row); + return

{severity}

; + }, + }, + { + accessorKey: "status", + header: "Status", + cell: ({ row }) => { + const { status } = getFindingsAttributes(row); + return

{status}

; + }, + }, + { + accessorKey: "region", + header: "Region", + cell: ({ row }) => { + const { region } = getFindingsAttributes(row); + return

{region}

; + }, + }, + { + accessorKey: "service", + header: "Service", + cell: ({ row }) => { + const { service } = getFindingsAttributes(row); + return

{service}

; + }, + }, + { + accessorKey: "account", + header: "Account", + cell: ({ row }) => { + const { account } = getFindingsAttributes(row); + return

{account}

; + }, + }, + { + accessorKey: "actions", + header: () => ( +
+ + + + + + Mute Findings for Selected Filters + Configure Muted Findings + + +
+ ), + }, +]; diff --git a/components/providers/table/SkeletonTableFindings.tsx b/components/providers/table/SkeletonTableFindings.tsx new file mode 100644 index 0000000000..074405d8a7 --- /dev/null +++ b/components/providers/table/SkeletonTableFindings.tsx @@ -0,0 +1,65 @@ +import { Card, Skeleton } from "@nextui-org/react"; +import React from "react"; + +export const SkeletonTableFindings = () => { + return ( + + {/* Table headers */} +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+ + {/* Table body */} +
+ {[...Array(3)].map((_, index) => ( +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+ ))} +
+
+ ); +}; diff --git a/types/components.ts b/types/components.ts index 994adf02a2..b7b53724e5 100644 --- a/types/components.ts +++ b/types/components.ts @@ -35,3 +35,15 @@ export interface ProviderProps { }; }; } + +export interface FindingsProps { + id: string; + attributes: { + CheckTitle: string; + severity: string; + status: string; + region: string; + service: string; + account: string; + }; +} From b95d48e2ad6b3d630c2269ee04bae3d53f44d86a Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Fri, 2 Aug 2024 10:24:47 +0200 Subject: [PATCH 087/411] chore: rendering real data for Providers and relocate action folder --- actions/index.ts | 1 + actions/providers.ts | 20 +++++++++++++++++++ app/(prowler)/providers/page.tsx | 9 ++------- .../providers/table/ColumnsProviders.tsx | 12 +++++------ lib/actions/index.ts | 1 - lib/actions/provider.actions.ts | 19 ------------------ 6 files changed, 29 insertions(+), 33 deletions(-) create mode 100644 actions/index.ts create mode 100644 actions/providers.ts delete mode 100644 lib/actions/index.ts delete mode 100644 lib/actions/provider.actions.ts diff --git a/actions/index.ts b/actions/index.ts new file mode 100644 index 0000000000..5532383f5f --- /dev/null +++ b/actions/index.ts @@ -0,0 +1 @@ +export * from "./providers"; diff --git a/actions/providers.ts b/actions/providers.ts new file mode 100644 index 0000000000..9ef428d1c5 --- /dev/null +++ b/actions/providers.ts @@ -0,0 +1,20 @@ +import "server-only"; + +import { parseStringify } from "@/lib"; + +export const getProvider = async () => { + const keyServer = process.env.LOCAL_SERVER_URL; + + try { + const providers = await fetch(`${keyServer}/providers`, { + headers: { + "X-Tenant-ID": "12646005-9067-4d2a-a098-8bb378604362", + }, + }); + + const data = await providers.json(); + return parseStringify(data); + } catch (error) { + return undefined; + } +}; diff --git a/app/(prowler)/providers/page.tsx b/app/(prowler)/providers/page.tsx index df2b7c01e8..e414558252 100644 --- a/app/(prowler)/providers/page.tsx +++ b/app/(prowler)/providers/page.tsx @@ -1,6 +1,7 @@ import { Spacer } from "@nextui-org/react"; import React, { Suspense } from "react"; +import { getProvider } from "@/actions"; import { ColumnsProviders, DataTable, @@ -8,7 +9,6 @@ import { ModalWrap, SkeletonTableProvider, } from "@/components"; -import { getProvider } from "@/lib/actions"; export default async function Providers() { const onSave = async () => { @@ -44,10 +44,5 @@ export default async function Providers() { const SSRDataTable = async () => { const providersData = await getProvider(); const [providers] = await Promise.all([providersData]); - return ( - - ); + return ; }; diff --git a/components/providers/table/ColumnsProviders.tsx b/components/providers/table/ColumnsProviders.tsx index 3701b68e2a..2d9f3ee22c 100644 --- a/components/providers/table/ColumnsProviders.tsx +++ b/components/providers/table/ColumnsProviders.tsx @@ -45,9 +45,9 @@ export const ColumnsProviders: ColumnDef[] = [ { accessorKey: "status", header: "Scan Status", - cell: ({ row }) => { - const { status } = getProviderAttributes(row); - return ; + cell: () => { + // Temporarily overwriting the value until the API is functional. + return ; }, }, { @@ -72,9 +72,9 @@ export const ColumnsProviders: ColumnDef[] = [ { accessorKey: "resources", header: "Resources", - cell: ({ row }) => { - const { resources } = getProviderAttributes(row); - return

{resources}

; + cell: () => { + // Temporarily overwriting the value until the API is functional. + return

{288}

; }, }, { diff --git a/lib/actions/index.ts b/lib/actions/index.ts deleted file mode 100644 index 67b97f1e4a..0000000000 --- a/lib/actions/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./provider.actions"; diff --git a/lib/actions/provider.actions.ts b/lib/actions/provider.actions.ts deleted file mode 100644 index c1973f0917..0000000000 --- a/lib/actions/provider.actions.ts +++ /dev/null @@ -1,19 +0,0 @@ -import "server-only"; - -import { parseStringify } from "../utils"; - -export const getProvider = async () => { - const key = process.env.LOCAL_SITE_URL; - - if (!key) return undefined; - - try { - const providers = await fetch(`${key}/api/providers`); - - const data = await providers.json(); - - return parseStringify(data); - } catch (error) { - return undefined; - } -}; From 26cfbeb3a88dd7edf5ef529627961ff394c7354b Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Fri, 2 Aug 2024 10:43:17 +0200 Subject: [PATCH 088/411] chore: add pending conecction for ProviderInfo component and its icon --- components/icons/Icons.tsx | 33 +++++++++++++++++++++++++++ components/providers/ProviderInfo.tsx | 13 +++++++++-- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/components/icons/Icons.tsx b/components/icons/Icons.tsx index aca8d6fc4d..77b070e067 100644 --- a/components/icons/Icons.tsx +++ b/components/icons/Icons.tsx @@ -271,6 +271,39 @@ export const WifiOffIcon: React.FC = ({ ); +export const WifiPendingIcon: React.FC = ({ + size = 24, + width, + height, + ...props +}) => ( + +); + export const RocketIcon: React.FC = ({ size = 24, width, diff --git a/components/providers/ProviderInfo.tsx b/components/providers/ProviderInfo.tsx index bc1e90124a..eb5962d530 100644 --- a/components/providers/ProviderInfo.tsx +++ b/components/providers/ProviderInfo.tsx @@ -6,10 +6,11 @@ import { GoogleCloudProvider, WifiIcon, WifiOffIcon, + WifiPendingIcon, } from "../icons"; interface ProviderInfoProps { - connected: boolean; + connected: boolean | null; provider: "aws" | "azure" | "gcp"; providerAlias: string; providerId: string; @@ -22,7 +23,15 @@ export const ProviderInfo: React.FC = ({ providerId, }) => { const getIcon = () => { - return connected ? : ; + switch (connected) { + case true: + return ; + case false: + return ; + case null: + default: + return ; + } }; const getProviderLogo = () => { From 0283b34190fe67644c132e0a8a7024e23f992da3 Mon Sep 17 00:00:00 2001 From: Sophia Dao Date: Fri, 2 Aug 2024 06:02:26 -0500 Subject: [PATCH 089/411] feat(findings): Fix folder structure --- components/{providers/table => findings}/ColumnsFindings.tsx | 0 .../{providers/table => findings}/SkeletonTableFindings.tsx | 0 components/index.ts | 4 ++-- 3 files changed, 2 insertions(+), 2 deletions(-) rename components/{providers/table => findings}/ColumnsFindings.tsx (100%) rename components/{providers/table => findings}/SkeletonTableFindings.tsx (100%) diff --git a/components/providers/table/ColumnsFindings.tsx b/components/findings/ColumnsFindings.tsx similarity index 100% rename from components/providers/table/ColumnsFindings.tsx rename to components/findings/ColumnsFindings.tsx diff --git a/components/providers/table/SkeletonTableFindings.tsx b/components/findings/SkeletonTableFindings.tsx similarity index 100% rename from components/providers/table/SkeletonTableFindings.tsx rename to components/findings/SkeletonTableFindings.tsx diff --git a/components/index.ts b/components/index.ts index 98f77ea0f2..9d76952378 100644 --- a/components/index.ts +++ b/components/index.ts @@ -1,10 +1,10 @@ +export * from "./findings/ColumnsFindings"; +export * from "./findings/SkeletonTableFindings"; export * from "./providers/DateWithTime"; export * from "./providers/ProviderInfo"; export * from "./providers/ScanStatus"; -export * from "./providers/table/ColumnsFindings"; export * from "./providers/table/ColumnsProviders"; export * from "./providers/table/DataTable"; -export * from "./providers/table/SkeletonTableFindings"; export * from "./providers/table/SkeletonTableProvider"; export * from "./ui/alert/Alert"; export * from "./ui/header/Header"; From 00613cdda38ce0cfdd5ae2effc257f0e9b600f2f Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Fri, 2 Aug 2024 18:43:58 +0200 Subject: [PATCH 090/411] add Toast library and handling server errors - WIP --- actions/providers.ts | 90 ++- app/(prowler)/layout.tsx | 2 + app/(prowler)/providers/page.tsx | 26 +- components/index.ts | 2 + components/providers/AddProvider.tsx | 58 ++ components/providers/ButtonAddProvider.tsx | 10 + .../providers/table/ColumnsProviders.tsx | 1 + components/ui/toast/Toast.tsx | 130 +++++ components/ui/toast/Toaster.tsx | 36 ++ components/ui/toast/index.ts | 3 + components/ui/toast/use-toast.ts | 191 +++++++ package-lock.json | 538 ++++++++++++++---- package.json | 2 + 13 files changed, 943 insertions(+), 146 deletions(-) create mode 100644 components/providers/AddProvider.tsx create mode 100644 components/providers/ButtonAddProvider.tsx create mode 100644 components/ui/toast/Toast.tsx create mode 100644 components/ui/toast/Toaster.tsx create mode 100644 components/ui/toast/index.ts create mode 100644 components/ui/toast/use-toast.ts diff --git a/actions/providers.ts b/actions/providers.ts index 9ef428d1c5..97f392ddcd 100644 --- a/actions/providers.ts +++ b/actions/providers.ts @@ -1,20 +1,82 @@ -import "server-only"; +// import "server-only"; + +"use server"; + +import { revalidatePath } from "next/cache"; +import { parseStringify } from '@/lib'; -import { parseStringify } from "@/lib"; export const getProvider = async () => { - const keyServer = process.env.LOCAL_SERVER_URL; + const keyServer = process.env.LOCAL_SERVER_URL; - try { - const providers = await fetch(`${keyServer}/providers`, { - headers: { - "X-Tenant-ID": "12646005-9067-4d2a-a098-8bb378604362", - }, - }); + try { + const providers = await fetch(`${keyServer}/providers`, { + headers: { + "X-Tenant-ID": "12646005-9067-4d2a-a098-8bb378604362", + }, + }); - const data = await providers.json(); - return parseStringify(data); - } catch (error) { - return undefined; - } + const data = await providers.json(); + return parseStringify(data); + } catch (error) { + return undefined; + } }; + +export const addProvider = async (formData: FormData) => { + const keyServer = process.env.LOCAL_SERVER_URL; + + const provider = formData.get("provider"); + const articleId = formData.get("id"); + const alias = formData.get("alias"); + console.log(provider, articleId, alias) + + try { + const response = await fetch(`${keyServer}/providers`, { + method: "POST", + headers: { + "X-Tenant-ID": "12646005-9067-4d2a-a098-8bb378604362", + "Content-Type": "application/vnd.api+json", + }, + body: JSON.stringify({ + data: { + type: "Provider", + attributes: { + provider: provider, + provider_id: articleId, + alias: alias, + }, + } + }), + }); + const data = await response.json(); + + console.log(data, 'triana') + if (!response.ok) { + throw new Error(data.message || "Request error"); + } + // return parseStringify(data); + } catch (error) { + console.error(error) + return { + error: getErrorMessage(error), + } + } + revalidatePath("/providers") + +}; + +export const getErrorMessage = (error: unknown): string => { + let message: string; + + if (error instanceof Error) { + message = error.message + } else if (error && typeof error === "object" && "message" in error) { + message = String(error.message) + } else if (typeof error === "string") { + message = error + } else { + message = "Wops! Something when wrong." + } + return message +} diff --git a/app/(prowler)/layout.tsx b/app/(prowler)/layout.tsx index 6ef0e1aaa4..cf932cfd20 100644 --- a/app/(prowler)/layout.tsx +++ b/app/(prowler)/layout.tsx @@ -3,6 +3,7 @@ import "@/styles/globals.css"; import { Metadata, Viewport } from "next"; import React from "react"; +import { Toaster } from "@/components"; import { SidebarWrap } from "@/components/ui/sidebar"; import { fontSans } from "@/config/fonts"; import { siteConfig } from "@/config/site"; @@ -48,6 +49,7 @@ export default function RootLayout({
{children} +
diff --git a/app/(prowler)/providers/page.tsx b/app/(prowler)/providers/page.tsx index e414558252..a6627e1cca 100644 --- a/app/(prowler)/providers/page.tsx +++ b/app/(prowler)/providers/page.tsx @@ -3,6 +3,7 @@ import React, { Suspense } from "react"; import { getProvider } from "@/actions"; import { + AddProvider, ColumnsProviders, DataTable, Header, @@ -21,17 +22,20 @@ export default async function Providers() {
- -

Modal body content

- - } - actionButtonLabel="Save" - onAction={onSave} - openButtonLabel="Add Cloud Accounts" - /> +
+ + +

Modal body content

+ + } + actionButtonLabel="Save" + onAction={onSave} + openButtonLabel="Add Cloud Accounts" + /> +
}> diff --git a/components/index.ts b/components/index.ts index 4cad0baacf..75ebceb23b 100644 --- a/components/index.ts +++ b/components/index.ts @@ -1,3 +1,4 @@ +export * from "./providers/AddProvider"; export * from "./providers/DateWithTime"; export * from "./providers/ProviderInfo"; export * from "./providers/ScanStatus"; @@ -10,3 +11,4 @@ export * from "./ui/modal"; export * from "./ui/sidebar"; export * from "./ui/table/StatusBadge"; export * from "./ui/table/Table"; +export * from "./ui/toast"; diff --git a/components/providers/AddProvider.tsx b/components/providers/AddProvider.tsx new file mode 100644 index 0000000000..2e9fa24a69 --- /dev/null +++ b/components/providers/AddProvider.tsx @@ -0,0 +1,58 @@ +"use client"; + +import { addProvider } from "@/actions"; +import { useRef } from "react"; +import { ButtonAddProvider } from "./ButtonAddProvider"; +import { toast, useToast } from "../ui/toast"; +import { ToastAction } from "@radix-ui/react-toast"; + +export const AddProvider = () => { + const ref = useRef(null) + const { toast } = useToast() + async function clientAction(formData:FormData) { + // reset the form + ref.current?.reset(); + // client-side validation + const result = await addProvider(formData) + if (result?.error) { + + const error = result.error + //show error + toast({ + title: `${error}`, + description: "There was a problem with your request.", + }) + } else { + toast({ + title: "Success!", + description: "The provider was added successfully.", + }) + } + + } + return ( +
+ + + + + + + + ); +}; diff --git a/components/providers/ButtonAddProvider.tsx b/components/providers/ButtonAddProvider.tsx new file mode 100644 index 0000000000..4a4b759649 --- /dev/null +++ b/components/providers/ButtonAddProvider.tsx @@ -0,0 +1,10 @@ +import { Button } from "@nextui-org/react"; +import React from "react"; +import { useFormStatus } from "react-dom"; + +export const ButtonAddProvider = () => { + const { pending } = useFormStatus(); + return ( + + ); +}; diff --git a/components/providers/table/ColumnsProviders.tsx b/components/providers/table/ColumnsProviders.tsx index 2d9f3ee22c..52204e5164 100644 --- a/components/providers/table/ColumnsProviders.tsx +++ b/components/providers/table/ColumnsProviders.tsx @@ -99,6 +99,7 @@ export const ColumnsProviders: ColumnDef[] = [ + Check connection Manage Delete diff --git a/components/ui/toast/Toast.tsx b/components/ui/toast/Toast.tsx new file mode 100644 index 0000000000..a00afa8fdd --- /dev/null +++ b/components/ui/toast/Toast.tsx @@ -0,0 +1,130 @@ +"use client"; + +import { Cross2Icon } from "@radix-ui/react-icons"; +import * as ToastPrimitives from "@radix-ui/react-toast"; +import { cva, type VariantProps } from "class-variance-authority"; +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +const ToastProvider = ToastPrimitives.Provider; + +const ToastViewport = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +ToastViewport.displayName = ToastPrimitives.Viewport.displayName; + +const toastVariants = cva( + "group pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-md border border-slate-200 p-4 pr-6 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full dark:border-slate-800", + { + variants: { + variant: { + default: + "border bg-white text-slate-950 dark:bg-slate-950 dark:text-slate-50", + destructive: + "destructive group border-red-500 bg-red-500 text-slate-50 dark:border-red-900 dark:bg-red-900 dark:text-slate-50", + }, + }, + defaultVariants: { + variant: "default", + }, + }, +); + +const Toast = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & + VariantProps +>(({ className, variant, ...props }, ref) => { + return ( + + ); +}); +Toast.displayName = ToastPrimitives.Root.displayName; + +const ToastAction = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +ToastAction.displayName = ToastPrimitives.Action.displayName; + +const ToastClose = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)); +ToastClose.displayName = ToastPrimitives.Close.displayName; + +const ToastTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +ToastTitle.displayName = ToastPrimitives.Title.displayName; + +const ToastDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +ToastDescription.displayName = ToastPrimitives.Description.displayName; + +type ToastProps = React.ComponentPropsWithoutRef; + +type ToastActionElement = React.ReactElement; + +export { + Toast, + ToastAction, + type ToastActionElement, + ToastClose, + ToastDescription, + type ToastProps, + ToastProvider, + ToastTitle, + ToastViewport, +}; diff --git a/components/ui/toast/Toaster.tsx b/components/ui/toast/Toaster.tsx new file mode 100644 index 0000000000..f264ad448b --- /dev/null +++ b/components/ui/toast/Toaster.tsx @@ -0,0 +1,36 @@ +"use client"; + +import { + Toast, + ToastClose, + ToastDescription, + ToastProvider, + ToastTitle, + ToastViewport, +} from "@/components"; + +import { useToast } from "./use-toast"; + +export function Toaster() { + const { toasts } = useToast(); + + return ( + + {toasts.map(function ({ id, title, description, action, ...props }) { + return ( + +
+ {title && {title}} + {description && ( + {description} + )} +
+ {action} + +
+ ); + })} + +
+ ); +} diff --git a/components/ui/toast/index.ts b/components/ui/toast/index.ts new file mode 100644 index 0000000000..d21acc2dc6 --- /dev/null +++ b/components/ui/toast/index.ts @@ -0,0 +1,3 @@ +export * from "./Toast"; +export * from "./Toaster"; +export * from "./use-toast"; diff --git a/components/ui/toast/use-toast.ts b/components/ui/toast/use-toast.ts new file mode 100644 index 0000000000..f6c7b68058 --- /dev/null +++ b/components/ui/toast/use-toast.ts @@ -0,0 +1,191 @@ +"use client"; + +// Inspired by react-hot-toast library +import * as React from "react"; + +import type { ToastActionElement, ToastProps } from "@/components"; + +const TOAST_LIMIT = 1; +const TOAST_REMOVE_DELAY = 1000000; + +type ToasterToast = ToastProps & { + id: string; + title?: React.ReactNode; + description?: React.ReactNode; + action?: ToastActionElement; +}; + +const actionTypes = { + ADD_TOAST: "ADD_TOAST", + UPDATE_TOAST: "UPDATE_TOAST", + DISMISS_TOAST: "DISMISS_TOAST", + REMOVE_TOAST: "REMOVE_TOAST", +} as const; + +let count = 0; + +function genId() { + count = (count + 1) % Number.MAX_SAFE_INTEGER; + return count.toString(); +} + +type ActionType = typeof actionTypes; + +type Action = + | { + type: ActionType["ADD_TOAST"]; + toast: ToasterToast; + } + | { + type: ActionType["UPDATE_TOAST"]; + toast: Partial; + } + | { + type: ActionType["DISMISS_TOAST"]; + toastId?: ToasterToast["id"]; + } + | { + type: ActionType["REMOVE_TOAST"]; + toastId?: ToasterToast["id"]; + }; + +interface State { + toasts: ToasterToast[]; +} + +const toastTimeouts = new Map>(); + +const addToRemoveQueue = (toastId: string) => { + if (toastTimeouts.has(toastId)) { + return; + } + + const timeout = setTimeout(() => { + toastTimeouts.delete(toastId); + dispatch({ + type: "REMOVE_TOAST", + toastId: toastId, + }); + }, TOAST_REMOVE_DELAY); + + toastTimeouts.set(toastId, timeout); +}; + +export const reducer = (state: State, action: Action): State => { + switch (action.type) { + case "ADD_TOAST": + return { + ...state, + toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT), + }; + + case "UPDATE_TOAST": + return { + ...state, + toasts: state.toasts.map((t) => + t.id === action.toast.id ? { ...t, ...action.toast } : t, + ), + }; + + case "DISMISS_TOAST": { + const { toastId } = action; + + // ! Side effects ! - This could be extracted into a dismissToast() action, + // but I'll keep it here for simplicity + if (toastId) { + addToRemoveQueue(toastId); + } else { + state.toasts.forEach((toast) => { + addToRemoveQueue(toast.id); + }); + } + + return { + ...state, + toasts: state.toasts.map((t) => + t.id === toastId || toastId === undefined + ? { + ...t, + open: false, + } + : t, + ), + }; + } + case "REMOVE_TOAST": + if (action.toastId === undefined) { + return { + ...state, + toasts: [], + }; + } + return { + ...state, + toasts: state.toasts.filter((t) => t.id !== action.toastId), + }; + } +}; + +const listeners: Array<(state: State) => void> = []; + +let memoryState: State = { toasts: [] }; + +function dispatch(action: Action) { + memoryState = reducer(memoryState, action); + listeners.forEach((listener) => { + listener(memoryState); + }); +} + +type Toast = Omit; + +function toast({ ...props }: Toast) { + const id = genId(); + + const update = (props: ToasterToast) => + dispatch({ + type: "UPDATE_TOAST", + toast: { ...props, id }, + }); + const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id }); + + dispatch({ + type: "ADD_TOAST", + toast: { + ...props, + id, + open: true, + onOpenChange: (open) => { + if (!open) dismiss(); + }, + }, + }); + + return { + id: id, + dismiss, + update, + }; +} + +function useToast() { + const [state, setState] = React.useState(memoryState); + + React.useEffect(() => { + listeners.push(setState); + return () => { + const index = listeners.indexOf(setState); + if (index > -1) { + listeners.splice(index, 1); + } + }; + }, [state]); + + return { + ...state, + toast, + dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }), + }; +} + +export { toast, useToast }; diff --git a/package-lock.json b/package-lock.json index 16e75f1e08..fa6ecc0a45 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,8 @@ "@nextui-org/react": "^2.4.2", "@nextui-org/system": "2.2.1", "@nextui-org/theme": "2.2.5", + "@radix-ui/react-icons": "^1.3.0", + "@radix-ui/react-toast": "^1.2.1", "@react-aria/ssr": "3.9.4", "@react-aria/visually-hidden": "3.8.12", "@tanstack/react-table": "^8.19.3", @@ -905,6 +907,126 @@ "node": ">= 10" } }, + "node_modules/@next/swc-darwin-x64": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.3.tgz", + "integrity": "sha512-6adp7waE6P1TYFSXpY366xwsOnEXM+y1kgRpjSRVI2CBDOcbRjsJ67Z6EgKIqWIue52d2q/Mx8g9MszARj8IEA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.3.tgz", + "integrity": "sha512-cuzCE/1G0ZSnTAHJPUT1rPgQx1w5tzSX7POXSLaS7w2nIUJUD+e25QoXD/hMfxbsT9rslEXugWypJMILBj/QsA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.3.tgz", + "integrity": "sha512-0D4/oMM2Y9Ta3nGuCcQN8jjJjmDPYpHX9OJzqk42NZGJocU2MqhBq5tWkJrUQOQY9N+In9xOdymzapM09GeiZw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.3.tgz", + "integrity": "sha512-ENPiNnBNDInBLyUU5ii8PMQh+4XLr4pG51tOp6aJ9xqFQ2iRI6IH0Ds2yJkAzNV1CfyagcyzPfROMViS2wOZ9w==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.3.tgz", + "integrity": "sha512-BTAbq0LnCbF5MtoM7I/9UeUu/8ZBY0i8SFjUMCbPDOLv+un67e2JgyN4pmgfXBwy/I+RHu8q+k+MCkDN6P9ViQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.3.tgz", + "integrity": "sha512-AEHIw/dhAMLNFJFJIJIyOFDzrzI5bAjI9J26gbO5xhAKHYTZ9Or04BesFPXiAYXDNdrwTP2dQceYA4dL1geu8A==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-ia32-msvc": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.3.tgz", + "integrity": "sha512-vga40n1q6aYb0CLrM+eEmisfKCR45ixQYXuBXxOOmmoV8sYST9k7E3US32FsY+CkkF7NtzdcebiFT4CHuMSyZw==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.3.tgz", + "integrity": "sha512-Q1/zm43RWynxrO7lW4ehciQVj+5ePBhOK+/K2P7pLFX3JaJ/IZVC69SHidrmZSOkqz7ECIOhhy7XhAFG4JYyHA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@nextui-org/accordion": { "version": "2.0.35", "resolved": "https://registry.npmjs.org/@nextui-org/accordion/-/accordion-2.0.35.tgz", @@ -2469,6 +2591,300 @@ "url": "https://opencollective.com/unts" } }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz", + "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==" + }, + "node_modules/@radix-ui/react-collection": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.0.tgz", + "integrity": "sha512-GZsZslMJEyo1VKm5L1ZJY8tGDxZNPAoUeQUIbKeJfoi7Q4kmig5AsgLMYYuyYbfjd8fBmFORAIwYAkXMnXZgZw==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-slot": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz", + "integrity": "sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.0.tgz", + "integrity": "sha512-OKrckBy+sMEgYM/sMmqmErVn0kZqrHPJze+Ql3DzYsDDp0hl0L62nx/2122/Bvps1qz645jlcu2tD9lrRSdf8A==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.0.tgz", + "integrity": "sha512-/UovfmmXGptwGcBQawLzvn2jOfM0t4z3/uKffoBlj724+n3FvBbZ7M0aaBOmkp6pqFYpO4yx8tSVJjx3Fl2jig==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-escape-keydown": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-icons": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-icons/-/react-icons-1.3.0.tgz", + "integrity": "sha512-jQxj/0LKgp+j9BiTXz3O3sgs26RNet2iLWmsPyRz2SIcR4q/4SbazXfnYwbAr+vLYKSfc7qxzyGQA1HLlYiuNw==", + "peerDependencies": { + "react": "^16.x || ^17.x || ^18.x" + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.1.tgz", + "integrity": "sha512-A3UtLk85UtqhzFqtoC8Q0KvR2GbXF3mtPgACSazajqq6A41mEQgo53iPzY4i6BwDxlIFqWIhiQ2G729n+2aw/g==", + "dependencies": { + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.0.tgz", + "integrity": "sha512-Gq6wuRN/asf9H/E/VzdKoUtT8GC9PQc9z40/vEr0VCJ4u5XvvhWIrSsCB6vD2/cH7ugTdSfYq9fLJCcM00acrQ==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz", + "integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==", + "dependencies": { + "@radix-ui/react-slot": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz", + "integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toast": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.1.tgz", + "integrity": "sha512-5trl7piMXcZiCq7MW6r8YYmu0bK5qDpTWz+FdEPdKyft2UixkspheYbjbrLXVN5NGKHFbOP7lm8eD0biiSqZqg==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-collection": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-dismissable-layer": "1.1.0", + "@radix-ui/react-portal": "1.1.1", + "@radix-ui/react-presence": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0", + "@radix-ui/react-visually-hidden": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz", + "integrity": "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz", + "integrity": "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.0.tgz", + "integrity": "sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz", + "integrity": "sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-visually-hidden": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.1.0.tgz", + "integrity": "sha512-N8MDZqtgCgG5S3aV60INAB475osJousYpZ4cTJ2cFbMpdHS5Y6loLTH8LPtkj2QN0x93J30HT/M3qJXM0+lyeQ==", + "dependencies": { + "@radix-ui/react-primitive": "2.0.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@react-aria/breadcrumbs": { "version": "3.5.13", "resolved": "https://registry.npmjs.org/@react-aria/breadcrumbs/-/breadcrumbs-3.5.13.tgz", @@ -3743,7 +4159,7 @@ "version": "18.3.0", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz", "integrity": "sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==", - "dev": true, + "devOptional": true, "dependencies": { "@types/react": "*" } @@ -10597,126 +11013,6 @@ "funding": { "url": "https://github.com/sponsors/colinhacks" } - }, - "node_modules/@next/swc-darwin-x64": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.3.tgz", - "integrity": "sha512-6adp7waE6P1TYFSXpY366xwsOnEXM+y1kgRpjSRVI2CBDOcbRjsJ67Z6EgKIqWIue52d2q/Mx8g9MszARj8IEA==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-gnu": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.3.tgz", - "integrity": "sha512-cuzCE/1G0ZSnTAHJPUT1rPgQx1w5tzSX7POXSLaS7w2nIUJUD+e25QoXD/hMfxbsT9rslEXugWypJMILBj/QsA==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-musl": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.3.tgz", - "integrity": "sha512-0D4/oMM2Y9Ta3nGuCcQN8jjJjmDPYpHX9OJzqk42NZGJocU2MqhBq5tWkJrUQOQY9N+In9xOdymzapM09GeiZw==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-gnu": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.3.tgz", - "integrity": "sha512-ENPiNnBNDInBLyUU5ii8PMQh+4XLr4pG51tOp6aJ9xqFQ2iRI6IH0Ds2yJkAzNV1CfyagcyzPfROMViS2wOZ9w==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-musl": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.3.tgz", - "integrity": "sha512-BTAbq0LnCbF5MtoM7I/9UeUu/8ZBY0i8SFjUMCbPDOLv+un67e2JgyN4pmgfXBwy/I+RHu8q+k+MCkDN6P9ViQ==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-arm64-msvc": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.3.tgz", - "integrity": "sha512-AEHIw/dhAMLNFJFJIJIyOFDzrzI5bAjI9J26gbO5xhAKHYTZ9Or04BesFPXiAYXDNdrwTP2dQceYA4dL1geu8A==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-ia32-msvc": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.3.tgz", - "integrity": "sha512-vga40n1q6aYb0CLrM+eEmisfKCR45ixQYXuBXxOOmmoV8sYST9k7E3US32FsY+CkkF7NtzdcebiFT4CHuMSyZw==", - "cpu": [ - "ia32" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-x64-msvc": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.3.tgz", - "integrity": "sha512-Q1/zm43RWynxrO7lW4ehciQVj+5ePBhOK+/K2P7pLFX3JaJ/IZVC69SHidrmZSOkqz7ECIOhhy7XhAFG4JYyHA==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } } } } diff --git a/package.json b/package.json index caad2dcdb8..d1837d47e3 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,8 @@ "@nextui-org/react": "^2.4.2", "@nextui-org/system": "2.2.1", "@nextui-org/theme": "2.2.5", + "@radix-ui/react-icons": "^1.3.0", + "@radix-ui/react-toast": "^1.2.1", "@react-aria/ssr": "3.9.4", "@react-aria/visually-hidden": "3.8.12", "@tanstack/react-table": "^8.19.3", From b16a7150fa58a91d69d4c19656e54dc3dc3a8271 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Sun, 4 Aug 2024 11:55:34 +0200 Subject: [PATCH 091/411] chore: add deleteProvider action --- actions/providers.ts | 36 +++++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/actions/providers.ts b/actions/providers.ts index 97f392ddcd..931345ce41 100644 --- a/actions/providers.ts +++ b/actions/providers.ts @@ -1,5 +1,3 @@ -// import "server-only"; - "use server"; import { revalidatePath } from "next/cache"; @@ -12,7 +10,7 @@ export const getProvider = async () => { try { const providers = await fetch(`${keyServer}/providers`, { headers: { - "X-Tenant-ID": "12646005-9067-4d2a-a098-8bb378604362", + "X-Tenant-ID": `${process.env.HEADER_TENANT_ID}`, }, }); @@ -27,15 +25,14 @@ export const addProvider = async (formData: FormData) => { const keyServer = process.env.LOCAL_SERVER_URL; const provider = formData.get("provider"); - const articleId = formData.get("id"); + const providerId = formData.get("id"); const alias = formData.get("alias"); - console.log(provider, articleId, alias) try { const response = await fetch(`${keyServer}/providers`, { method: "POST", headers: { - "X-Tenant-ID": "12646005-9067-4d2a-a098-8bb378604362", + "X-Tenant-ID": `${process.env.HEADER_TENANT_ID}`, "Content-Type": "application/vnd.api+json", }, body: JSON.stringify({ @@ -43,7 +40,7 @@ export const addProvider = async (formData: FormData) => { type: "Provider", attributes: { provider: provider, - provider_id: articleId, + provider_id: providerId, alias: alias, }, } @@ -51,11 +48,32 @@ export const addProvider = async (formData: FormData) => { }); const data = await response.json(); - console.log(data, 'triana') if (!response.ok) { throw new Error(data.message || "Request error"); } - // return parseStringify(data); + } catch (error) { + console.error(error) + return { + error: getErrorMessage(error), + } + } + revalidatePath("/providers") + +}; + +export const deleteProvider = async (formData: FormData) => { + const keyServer = process.env.LOCAL_SERVER_URL; + + const providerId = formData.get("id"); + + try { + await fetch(`${keyServer}/providers/${providerId}`, { + method: "DELETE", + headers: { + "X-Tenant-ID": `${process.env.HEADER_TENANT_ID}`, + }, + }); + } catch (error) { console.error(error) return { From 485482c86811396f5a2b80929a31d2b7365557eb Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Sun, 4 Aug 2024 18:18:53 +0200 Subject: [PATCH 092/411] feat: GET and POST provider are working as expected and the error is shown correctly --- actions/providers.ts | 12 +--- components/icons/Icons.tsx | 24 ++++++++ components/index.ts | 2 + components/providers/AddProvider.tsx | 27 +++++---- components/providers/ButtonAddProvider.tsx | 6 +- components/providers/ButtonDeleteProvider.tsx | 21 +++++++ components/providers/DeleteProvider.tsx | 58 +++++++++++++++++++ .../providers/table/ColumnsProviders.tsx | 21 ++++--- components/ui/toast/use-toast.ts | 2 +- 9 files changed, 139 insertions(+), 34 deletions(-) create mode 100644 components/providers/ButtonDeleteProvider.tsx create mode 100644 components/providers/DeleteProvider.tsx diff --git a/actions/providers.ts b/actions/providers.ts index 931345ce41..85674a1545 100644 --- a/actions/providers.ts +++ b/actions/providers.ts @@ -13,8 +13,8 @@ export const getProvider = async () => { "X-Tenant-ID": `${process.env.HEADER_TENANT_ID}`, }, }); - const data = await providers.json(); + revalidatePath("/providers") return parseStringify(data); } catch (error) { return undefined; @@ -47,10 +47,8 @@ export const addProvider = async (formData: FormData) => { }), }); const data = await response.json(); - - if (!response.ok) { - throw new Error(data.message || "Request error"); - } + revalidatePath("/providers") + return parseStringify(data); } catch (error) { console.error(error) return { @@ -58,7 +56,6 @@ export const addProvider = async (formData: FormData) => { } } revalidatePath("/providers") - }; export const deleteProvider = async (formData: FormData) => { @@ -73,15 +70,12 @@ export const deleteProvider = async (formData: FormData) => { "X-Tenant-ID": `${process.env.HEADER_TENANT_ID}`, }, }); - } catch (error) { - console.error(error) return { error: getErrorMessage(error), } } revalidatePath("/providers") - }; export const getErrorMessage = (error: unknown): string => { diff --git a/components/icons/Icons.tsx b/components/icons/Icons.tsx index 77b070e067..55b52918f6 100644 --- a/components/icons/Icons.tsx +++ b/components/icons/Icons.tsx @@ -304,6 +304,30 @@ export const WifiPendingIcon: React.FC = ({ ); +export const DeleteIcon: React.FC = ({ + size = 24, + width, + height, + ...props +}) => ( + +); + export const RocketIcon: React.FC = ({ size = 24, width, diff --git a/components/index.ts b/components/index.ts index 75ebceb23b..b970de3682 100644 --- a/components/index.ts +++ b/components/index.ts @@ -1,4 +1,6 @@ export * from "./providers/AddProvider"; +export * from "./providers/ButtonAddProvider"; +export * from "./providers/ButtonDeleteProvider"; export * from "./providers/DateWithTime"; export * from "./providers/ProviderInfo"; export * from "./providers/ScanStatus"; diff --git a/components/providers/AddProvider.tsx b/components/providers/AddProvider.tsx index 2e9fa24a69..8c7fbf74cc 100644 --- a/components/providers/AddProvider.tsx +++ b/components/providers/AddProvider.tsx @@ -2,34 +2,35 @@ import { addProvider } from "@/actions"; import { useRef } from "react"; +import { useToast } from "../ui/toast"; import { ButtonAddProvider } from "./ButtonAddProvider"; -import { toast, useToast } from "../ui/toast"; -import { ToastAction } from "@radix-ui/react-toast"; export const AddProvider = () => { const ref = useRef(null) const { toast } = useToast() + async function clientAction(formData:FormData) { // reset the form ref.current?.reset(); // client-side validation const result = await addProvider(formData) - if (result?.error) { - - const error = result.error - //show error - toast({ - title: `${error}`, - description: "There was a problem with your request.", - }) + if (result?.errors) { + result.errors.forEach((error: { detail: string }) => { + let errorMessage = `${error.detail}`; + // show error + toast({ + title: "Wops! Something went wrong", + description: errorMessage, + }); + }); } else { toast({ title: "Success!", description: "The provider was added successfully.", - }) + }); } - } + return (
{ className="py-2 px-3 rounded-sm" /> - - ); }; diff --git a/components/providers/ButtonAddProvider.tsx b/components/providers/ButtonAddProvider.tsx index 4a4b759649..d4b82e1064 100644 --- a/components/providers/ButtonAddProvider.tsx +++ b/components/providers/ButtonAddProvider.tsx @@ -1,3 +1,5 @@ +"use client"; + import { Button } from "@nextui-org/react"; import React from "react"; import { useFormStatus } from "react-dom"; @@ -5,6 +7,8 @@ import { useFormStatus } from "react-dom"; export const ButtonAddProvider = () => { const { pending } = useFormStatus(); return ( - + ); }; diff --git a/components/providers/ButtonDeleteProvider.tsx b/components/providers/ButtonDeleteProvider.tsx new file mode 100644 index 0000000000..797e1a5022 --- /dev/null +++ b/components/providers/ButtonDeleteProvider.tsx @@ -0,0 +1,21 @@ +"use client"; + +import { Button } from "@nextui-org/react"; +import React from "react"; +import { useFormStatus } from "react-dom"; + +import { DeleteIcon } from "../icons"; + +export const ButtonDeleteProvider = () => { + const { pending } = useFormStatus(); + return ( + + ); +}; diff --git a/components/providers/DeleteProvider.tsx b/components/providers/DeleteProvider.tsx new file mode 100644 index 0000000000..72812fdc13 --- /dev/null +++ b/components/providers/DeleteProvider.tsx @@ -0,0 +1,58 @@ +"use client"; + +import { useRef } from "react"; + +import { deleteProvider } from "@/actions"; + +import { useToast } from "../ui/toast"; +import { ButtonDeleteProvider } from "./ButtonDeleteProvider"; + +export const DeleteProvider = ({ id }: { id: string }) => { + const ref = useRef(null); + const { toast } = useToast(); + + // const [state, formAction] = useFormState(deleteProvider, initialState); + + async function clientAction(formData: FormData) { + // reset the form + ref.current?.reset(); + // client-side validation + const result = await deleteProvider(formData); + if (result?.error) { + const error = result.error; + //show error + toast({ + title: `${error}`, + description: "There was a problem with your request.", + }); + } else { + toast({ + title: "Success!", + description: "The provider was removed successfully.", + }); + } + } + return ( +
+ + + + {/*

+ {state?.message} +

*/} + + ); +}; + +// const [state, formAction] = useFormState(deleteTodo, initialState); + +// return ( +//
+// +// +// +//

+// {state?.message} +//

+// +// ); diff --git a/components/providers/table/ColumnsProviders.tsx b/components/providers/table/ColumnsProviders.tsx index 52204e5164..82529c2cab 100644 --- a/components/providers/table/ColumnsProviders.tsx +++ b/components/providers/table/ColumnsProviders.tsx @@ -16,9 +16,10 @@ import { ProviderProps } from "@/types"; import { DateWithTime } from "../DateWithTime"; import { ProviderInfo } from "../ProviderInfo"; +import { DeleteProvider } from "../DeleteProvider"; -const getProviderAttributes = (row: { original: ProviderProps }) => { - return row.original.attributes; +const getProviderData= (row: { original: ProviderProps }) => { + return row.original; }; export const ColumnsProviders: ColumnDef[] = [ @@ -30,8 +31,7 @@ export const ColumnsProviders: ColumnDef[] = [ accessorKey: "account", header: "Account", cell: ({ row }) => { - const { connection, provider, alias, provider_id } = - getProviderAttributes(row); + const { attributes: { connection, provider, alias, provider_id } } = getProviderData(row); return ( [] = [ accessorKey: "lastScan", header: "Last Scan", cell: ({ row }) => { - const { updated_at } = getProviderAttributes(row); + const { attributes: {updated_at} } = getProviderData(row); return ; }, }, @@ -62,7 +62,7 @@ export const ColumnsProviders: ColumnDef[] = [ accessorKey: "nextScan", header: "Next Scan", cell: ({ row }) => { - const { updated_at } = getProviderAttributes(row); + const { attributes: {updated_at} } = getProviderData(row); const nextDay = add(new Date(updated_at), { hours: 24, }); @@ -81,7 +81,7 @@ export const ColumnsProviders: ColumnDef[] = [ accessorKey: "added", header: "Added", cell: ({ row }) => { - const { inserted_at } = getProviderAttributes(row); + const { attributes: {inserted_at} } = getProviderData(row); return ; }, }, @@ -89,7 +89,8 @@ export const ColumnsProviders: ColumnDef[] = [ accessorKey: "actions", header: () =>
Actions
, id: "actions", - cell: () => { + cell: ({ row }) => { + const { id } = getProviderData(row); return (
@@ -101,7 +102,9 @@ export const ColumnsProviders: ColumnDef[] = [ Check connection Manage - Delete + + +
diff --git a/components/ui/toast/use-toast.ts b/components/ui/toast/use-toast.ts index f6c7b68058..1a7c514a81 100644 --- a/components/ui/toast/use-toast.ts +++ b/components/ui/toast/use-toast.ts @@ -5,7 +5,7 @@ import * as React from "react"; import type { ToastActionElement, ToastProps } from "@/components"; -const TOAST_LIMIT = 1; +const TOAST_LIMIT = 6; const TOAST_REMOVE_DELAY = 1000000; type ToasterToast = ToastProps & { From ffb91d27330d4d6cde80a7dd304b91ddf9e5fadc Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Sun, 4 Aug 2024 18:55:20 +0200 Subject: [PATCH 093/411] feat: method POST to check the provider connection is working --- actions/providers.ts | 30 ++++++++++++-- components/icons/Icons.tsx | 20 ++++++++++ components/index.ts | 2 + components/providers/AddProvider.tsx | 6 +-- .../ButtonCheckConnectionProvider.tsx | 21 ++++++++++ .../providers/CheckConnectionProvider.tsx | 40 +++++++++++++++++++ components/providers/DeleteProvider.tsx | 32 ++++----------- .../providers/table/ColumnsProviders.tsx | 4 +- 8 files changed, 122 insertions(+), 33 deletions(-) create mode 100644 components/providers/ButtonCheckConnectionProvider.tsx create mode 100644 components/providers/CheckConnectionProvider.tsx diff --git a/actions/providers.ts b/actions/providers.ts index 85674a1545..27a01e688a 100644 --- a/actions/providers.ts +++ b/actions/providers.ts @@ -21,6 +21,7 @@ export const getProvider = async () => { } }; + export const addProvider = async (formData: FormData) => { const keyServer = process.env.LOCAL_SERVER_URL; @@ -58,26 +59,49 @@ export const addProvider = async (formData: FormData) => { revalidatePath("/providers") }; +export const checkConnectionProvider = async (formData: FormData) => { + const keyServer = process.env.LOCAL_SERVER_URL; + + const providerId = formData.get("id"); + + try { + const response = await fetch(`${keyServer}/providers/${providerId}/connection`, { + method: "POST", + headers: { + "X-Tenant-ID": `${process.env.HEADER_TENANT_ID}`, + }, + }); + const data = await response.json(); + revalidatePath("/providers") + return parseStringify(data); + } catch (error) { + return { + error: getErrorMessage(error), + } + } +}; + export const deleteProvider = async (formData: FormData) => { const keyServer = process.env.LOCAL_SERVER_URL; const providerId = formData.get("id"); try { - await fetch(`${keyServer}/providers/${providerId}`, { + const response = await fetch(`${keyServer}/providers/${providerId}`, { method: "DELETE", headers: { "X-Tenant-ID": `${process.env.HEADER_TENANT_ID}`, }, }); + const data = await response.json(); + revalidatePath("/providers") + return parseStringify(data); } catch (error) { return { error: getErrorMessage(error), } } - revalidatePath("/providers") }; - export const getErrorMessage = (error: unknown): string => { let message: string; diff --git a/components/icons/Icons.tsx b/components/icons/Icons.tsx index 55b52918f6..647589639c 100644 --- a/components/icons/Icons.tsx +++ b/components/icons/Icons.tsx @@ -328,6 +328,26 @@ export const DeleteIcon: React.FC = ({ ); +export const CheckIcon: React.FC = ({ + size = 48, + width, + height, + ...props +}) => ( + + + +); + export const RocketIcon: React.FC = ({ size = 24, width, diff --git a/components/index.ts b/components/index.ts index b970de3682..be49cd41db 100644 --- a/components/index.ts +++ b/components/index.ts @@ -1,6 +1,8 @@ export * from "./providers/AddProvider"; export * from "./providers/ButtonAddProvider"; +export * from "./providers/ButtonCheckConnectionProvider"; export * from "./providers/ButtonDeleteProvider"; +export * from "./providers/CheckConnectionProvider"; export * from "./providers/DateWithTime"; export * from "./providers/ProviderInfo"; export * from "./providers/ScanStatus"; diff --git a/components/providers/AddProvider.tsx b/components/providers/AddProvider.tsx index 8c7fbf74cc..c53e267e01 100644 --- a/components/providers/AddProvider.tsx +++ b/components/providers/AddProvider.tsx @@ -13,9 +13,9 @@ export const AddProvider = () => { // reset the form ref.current?.reset(); // client-side validation - const result = await addProvider(formData) - if (result?.errors) { - result.errors.forEach((error: { detail: string }) => { + const data = await addProvider(formData) + if (data?.errors) { + data.errors.forEach((error: { detail: string }) => { let errorMessage = `${error.detail}`; // show error toast({ diff --git a/components/providers/ButtonCheckConnectionProvider.tsx b/components/providers/ButtonCheckConnectionProvider.tsx new file mode 100644 index 0000000000..9c3076de78 --- /dev/null +++ b/components/providers/ButtonCheckConnectionProvider.tsx @@ -0,0 +1,21 @@ +"use client"; + +import { Button } from "@nextui-org/react"; +import React from "react"; +import { useFormStatus } from "react-dom"; + +import { CheckIcon } from "../icons"; + +export const ButtonCheckConnectionProvider = () => { + const { pending } = useFormStatus(); + return ( + + ); +}; diff --git a/components/providers/CheckConnectionProvider.tsx b/components/providers/CheckConnectionProvider.tsx new file mode 100644 index 0000000000..c8f1b190b7 --- /dev/null +++ b/components/providers/CheckConnectionProvider.tsx @@ -0,0 +1,40 @@ +"use client"; + +import { useRef } from "react"; + +import { checkConnectionProvider } from "@/actions"; + +import { useToast } from "../ui/toast"; +import { ButtonCheckConnectionProvider } from "./ButtonCheckConnectionProvider"; + +export const CheckConnectionProvider = ({ id }: { id: string }) => { + const ref = useRef(null); + const { toast } = useToast(); + + async function clientAction(formData: FormData) { + // reset the form + ref.current?.reset(); + // client-side validation + const data = await checkConnectionProvider(formData); + if (data?.errors && data.errors.length > 0) { + const error = data.errors[0]; + const errorMessage = `${error.detail}`; + // show error + toast({ + title: "Wops! Something went wrong", + description: errorMessage, + }); + } else { + toast({ + title: "Success!", + description: "The connection was updated successfully.", + }); + } + } + return ( +
+ + + + ); +}; diff --git a/components/providers/DeleteProvider.tsx b/components/providers/DeleteProvider.tsx index 72812fdc13..8a269e13d7 100644 --- a/components/providers/DeleteProvider.tsx +++ b/components/providers/DeleteProvider.tsx @@ -11,19 +11,18 @@ export const DeleteProvider = ({ id }: { id: string }) => { const ref = useRef(null); const { toast } = useToast(); - // const [state, formAction] = useFormState(deleteProvider, initialState); - async function clientAction(formData: FormData) { // reset the form ref.current?.reset(); // client-side validation - const result = await deleteProvider(formData); - if (result?.error) { - const error = result.error; - //show error + const data = await deleteProvider(formData); + if (data?.errors && data.errors.length > 0) { + const error = data.errors[0]; + const errorMessage = `${error.detail}`; + // show error toast({ - title: `${error}`, - description: "There was a problem with your request.", + title: "Wops! Something went wrong", + description: errorMessage, }); } else { toast({ @@ -35,24 +34,7 @@ export const DeleteProvider = ({ id }: { id: string }) => { return (
- - {/*

- {state?.message} -

*/} ); }; - -// const [state, formAction] = useFormState(deleteTodo, initialState); - -// return ( -//
-// -// -// -//

-// {state?.message} -//

-// -// ); diff --git a/components/providers/table/ColumnsProviders.tsx b/components/providers/table/ColumnsProviders.tsx index 82529c2cab..e07ad2f299 100644 --- a/components/providers/table/ColumnsProviders.tsx +++ b/components/providers/table/ColumnsProviders.tsx @@ -17,6 +17,7 @@ import { ProviderProps } from "@/types"; import { DateWithTime } from "../DateWithTime"; import { ProviderInfo } from "../ProviderInfo"; import { DeleteProvider } from "../DeleteProvider"; +import { CheckConnectionProvider } from "../CheckConnectionProvider"; const getProviderData= (row: { original: ProviderProps }) => { return row.original; @@ -100,8 +101,7 @@ export const ColumnsProviders: ColumnDef[] = [ - Check connection - Manage + From a2172d12f4dfb875698d5e3ca192cee479f0d34c Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Sun, 4 Aug 2024 20:02:55 +0200 Subject: [PATCH 094/411] fix: run the lint fix to be able to build the app --- actions/providers.ts | 188 +++++++++--------- components/providers/AddProvider.tsx | 16 +- .../providers/table/ColumnsProviders.tsx | 28 ++- 3 files changed, 123 insertions(+), 109 deletions(-) diff --git a/actions/providers.ts b/actions/providers.ts index 27a01e688a..e339883d24 100644 --- a/actions/providers.ts +++ b/actions/providers.ts @@ -1,118 +1,120 @@ "use server"; import { revalidatePath } from "next/cache"; -import { parseStringify } from '@/lib'; +import { parseStringify } from "@/lib"; export const getProvider = async () => { - const keyServer = process.env.LOCAL_SERVER_URL; + const keyServer = process.env.LOCAL_SERVER_URL; - try { - const providers = await fetch(`${keyServer}/providers`, { - headers: { - "X-Tenant-ID": `${process.env.HEADER_TENANT_ID}`, - }, - }); - const data = await providers.json(); - revalidatePath("/providers") - return parseStringify(data); - } catch (error) { - return undefined; - } + try { + const providers = await fetch(`${keyServer}/providers`, { + headers: { + "X-Tenant-ID": `${process.env.HEADER_TENANT_ID}`, + }, + }); + const data = await providers.json(); + revalidatePath("/providers"); + return parseStringify(data); + } catch (error) { + return undefined; + } }; - export const addProvider = async (formData: FormData) => { - const keyServer = process.env.LOCAL_SERVER_URL; + const keyServer = process.env.LOCAL_SERVER_URL; - const provider = formData.get("provider"); - const providerId = formData.get("id"); - const alias = formData.get("alias"); + const provider = formData.get("provider"); + const providerId = formData.get("id"); + const alias = formData.get("alias"); - try { - const response = await fetch(`${keyServer}/providers`, { - method: "POST", - headers: { - "X-Tenant-ID": `${process.env.HEADER_TENANT_ID}`, - "Content-Type": "application/vnd.api+json", - }, - body: JSON.stringify({ - data: { - type: "Provider", - attributes: { - provider: provider, - provider_id: providerId, - alias: alias, - }, - } - }), - }); - const data = await response.json(); - revalidatePath("/providers") - return parseStringify(data); - } catch (error) { - console.error(error) - return { - error: getErrorMessage(error), - } - } - revalidatePath("/providers") + try { + const response = await fetch(`${keyServer}/providers`, { + method: "POST", + headers: { + "X-Tenant-ID": `${process.env.HEADER_TENANT_ID}`, + "Content-Type": "application/vnd.api+json", + }, + body: JSON.stringify({ + data: { + type: "Provider", + attributes: { + provider: provider, + provider_id: providerId, + alias: alias, + }, + }, + }), + }); + const data = await response.json(); + revalidatePath("/providers"); + return parseStringify(data); + } catch (error) { + console.error(error); + return { + error: getErrorMessage(error), + }; + } + revalidatePath("/providers"); }; export const checkConnectionProvider = async (formData: FormData) => { - const keyServer = process.env.LOCAL_SERVER_URL; + const keyServer = process.env.LOCAL_SERVER_URL; - const providerId = formData.get("id"); + const providerId = formData.get("id"); - try { - const response = await fetch(`${keyServer}/providers/${providerId}/connection`, { - method: "POST", - headers: { - "X-Tenant-ID": `${process.env.HEADER_TENANT_ID}`, - }, - }); - const data = await response.json(); - revalidatePath("/providers") - return parseStringify(data); - } catch (error) { - return { - error: getErrorMessage(error), - } - } + try { + const response = await fetch( + `${keyServer}/providers/${providerId}/connection`, + { + method: "POST", + headers: { + "X-Tenant-ID": `${process.env.HEADER_TENANT_ID}`, + }, + }, + ); + const data = await response.json(); + revalidatePath("/providers"); + return parseStringify(data); + } catch (error) { + return { + error: getErrorMessage(error), + }; + } }; export const deleteProvider = async (formData: FormData) => { - const keyServer = process.env.LOCAL_SERVER_URL; + const keyServer = process.env.LOCAL_SERVER_URL; - const providerId = formData.get("id"); + const providerId = formData.get("id"); - try { - const response = await fetch(`${keyServer}/providers/${providerId}`, { - method: "DELETE", - headers: { - "X-Tenant-ID": `${process.env.HEADER_TENANT_ID}`, - }, - }); - const data = await response.json(); - revalidatePath("/providers") - return parseStringify(data); - } catch (error) { - return { - error: getErrorMessage(error), - } - } + try { + const response = await fetch(`${keyServer}/providers/${providerId}`, { + method: "DELETE", + headers: { + "X-Tenant-ID": `${process.env.HEADER_TENANT_ID}`, + }, + }); + const data = await response.json(); + revalidatePath("/providers"); + return parseStringify(data); + } catch (error) { + return { + error: getErrorMessage(error), + }; + } }; export const getErrorMessage = (error: unknown): string => { - let message: string; + let message: string; - if (error instanceof Error) { - message = error.message - } else if (error && typeof error === "object" && "message" in error) { - message = String(error.message) - } else if (typeof error === "string") { - message = error - } else { - message = "Wops! Something when wrong." - } - return message -} + if (error instanceof Error) { + message = error.message; + } else if (error && typeof error === "object" && "message" in error) { + message = String(error.message); + } else if (typeof error === "string") { + message = error; + } else { + message = "Wops! Something when wrong."; + } + return message; +}; diff --git a/components/providers/AddProvider.tsx b/components/providers/AddProvider.tsx index c53e267e01..74f429e4c6 100644 --- a/components/providers/AddProvider.tsx +++ b/components/providers/AddProvider.tsx @@ -1,22 +1,24 @@ "use client"; -import { addProvider } from "@/actions"; import { useRef } from "react"; + +import { addProvider } from "@/actions"; + import { useToast } from "../ui/toast"; import { ButtonAddProvider } from "./ButtonAddProvider"; export const AddProvider = () => { - const ref = useRef(null) - const { toast } = useToast() - - async function clientAction(formData:FormData) { + const ref = useRef(null); + const { toast } = useToast(); + + async function clientAction(formData: FormData) { // reset the form ref.current?.reset(); // client-side validation - const data = await addProvider(formData) + const data = await addProvider(formData); if (data?.errors) { data.errors.forEach((error: { detail: string }) => { - let errorMessage = `${error.detail}`; + const errorMessage = `${error.detail}`; // show error toast({ title: "Wops! Something went wrong", diff --git a/components/providers/table/ColumnsProviders.tsx b/components/providers/table/ColumnsProviders.tsx index e07ad2f299..7c2fbd2982 100644 --- a/components/providers/table/ColumnsProviders.tsx +++ b/components/providers/table/ColumnsProviders.tsx @@ -14,12 +14,12 @@ import { VerticalDotsIcon } from "@/components/icons"; import { StatusBadge } from "@/components/ui/table/StatusBadge"; import { ProviderProps } from "@/types"; -import { DateWithTime } from "../DateWithTime"; -import { ProviderInfo } from "../ProviderInfo"; -import { DeleteProvider } from "../DeleteProvider"; import { CheckConnectionProvider } from "../CheckConnectionProvider"; +import { DateWithTime } from "../DateWithTime"; +import { DeleteProvider } from "../DeleteProvider"; +import { ProviderInfo } from "../ProviderInfo"; -const getProviderData= (row: { original: ProviderProps }) => { +const getProviderData = (row: { original: ProviderProps }) => { return row.original; }; @@ -32,7 +32,9 @@ export const ColumnsProviders: ColumnDef[] = [ accessorKey: "account", header: "Account", cell: ({ row }) => { - const { attributes: { connection, provider, alias, provider_id } } = getProviderData(row); + const { + attributes: { connection, provider, alias, provider_id }, + } = getProviderData(row); return ( [] = [ accessorKey: "lastScan", header: "Last Scan", cell: ({ row }) => { - const { attributes: {updated_at} } = getProviderData(row); + const { + attributes: { updated_at }, + } = getProviderData(row); return ; }, }, @@ -63,7 +67,9 @@ export const ColumnsProviders: ColumnDef[] = [ accessorKey: "nextScan", header: "Next Scan", cell: ({ row }) => { - const { attributes: {updated_at} } = getProviderData(row); + const { + attributes: { updated_at }, + } = getProviderData(row); const nextDay = add(new Date(updated_at), { hours: 24, }); @@ -82,7 +88,9 @@ export const ColumnsProviders: ColumnDef[] = [ accessorKey: "added", header: "Added", cell: ({ row }) => { - const { attributes: {inserted_at} } = getProviderData(row); + const { + attributes: { inserted_at }, + } = getProviderData(row); return ; }, }, @@ -101,7 +109,9 @@ export const ColumnsProviders: ColumnDef[] = [ - + + + From ff3f90ac940ab07446e23e36c454096540c444ab Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Mon, 5 Aug 2024 09:39:18 +0200 Subject: [PATCH 095/411] chore: replace CrossIcon for the toast and change variants when error --- components/icons/Icons.tsx | 46 ++++++++++++++++++- components/providers/AddProvider.tsx | 5 +- .../providers/CheckConnectionProvider.tsx | 3 +- components/providers/DeleteProvider.tsx | 3 +- components/ui/toast/Toast.tsx | 8 ++-- 5 files changed, 56 insertions(+), 9 deletions(-) diff --git a/components/icons/Icons.tsx b/components/icons/Icons.tsx index 647589639c..485baf39d4 100644 --- a/components/icons/Icons.tsx +++ b/components/icons/Icons.tsx @@ -329,7 +329,7 @@ export const DeleteIcon: React.FC = ({ ); export const CheckIcon: React.FC = ({ - size = 48, + size = 24, width, height, ...props @@ -348,6 +348,50 @@ export const CheckIcon: React.FC = ({ ); +export const CrossIcon: React.FC = ({ + size = 24, + width, + height, + ...props +}) => ( + + + +); + +export const PassIcon: React.FC = ({ + size = 24, + width, + height, + ...props +}) => ( + + + + + + +); + export const RocketIcon: React.FC = ({ size = 24, width, diff --git a/components/providers/AddProvider.tsx b/components/providers/AddProvider.tsx index 74f429e4c6..ba914f01fd 100644 --- a/components/providers/AddProvider.tsx +++ b/components/providers/AddProvider.tsx @@ -21,8 +21,9 @@ export const AddProvider = () => { const errorMessage = `${error.detail}`; // show error toast({ - title: "Wops! Something went wrong", - description: errorMessage, + variant: "destructive", + title: "Ups! Something went wrong", + description: errorMessage }); }); } else { diff --git a/components/providers/CheckConnectionProvider.tsx b/components/providers/CheckConnectionProvider.tsx index c8f1b190b7..598b93df66 100644 --- a/components/providers/CheckConnectionProvider.tsx +++ b/components/providers/CheckConnectionProvider.tsx @@ -21,7 +21,8 @@ export const CheckConnectionProvider = ({ id }: { id: string }) => { const errorMessage = `${error.detail}`; // show error toast({ - title: "Wops! Something went wrong", + variant: "destructive", + title: "Ups! Something went wrong", description: errorMessage, }); } else { diff --git a/components/providers/DeleteProvider.tsx b/components/providers/DeleteProvider.tsx index 8a269e13d7..6d5b476253 100644 --- a/components/providers/DeleteProvider.tsx +++ b/components/providers/DeleteProvider.tsx @@ -21,7 +21,8 @@ export const DeleteProvider = ({ id }: { id: string }) => { const errorMessage = `${error.detail}`; // show error toast({ - title: "Wops! Something went wrong", + variant: "destructive", + title: "Ups! Something went wrong", description: errorMessage, }); } else { diff --git a/components/ui/toast/Toast.tsx b/components/ui/toast/Toast.tsx index a00afa8fdd..dba0e5a784 100644 --- a/components/ui/toast/Toast.tsx +++ b/components/ui/toast/Toast.tsx @@ -1,10 +1,10 @@ "use client"; -import { Cross2Icon } from "@radix-ui/react-icons"; import * as ToastPrimitives from "@radix-ui/react-toast"; import { cva, type VariantProps } from "class-variance-authority"; import * as React from "react"; +import { CrossIcon } from "@/components/icons"; import { cn } from "@/lib/utils"; const ToastProvider = ToastPrimitives.Provider; @@ -84,7 +84,7 @@ const ToastClose = React.forwardRef< toast-close="" {...props} > - + )); ToastClose.displayName = ToastPrimitives.Close.displayName; @@ -95,7 +95,7 @@ const ToastTitle = React.forwardRef< >(({ className, ...props }, ref) => ( )); @@ -107,7 +107,7 @@ const ToastDescription = React.forwardRef< >(({ className, ...props }, ref) => ( )); From e7f79589d4ea52ae7fc9f43016d1c59f46163306 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Mon, 5 Aug 2024 13:09:44 +0200 Subject: [PATCH 096/411] feat: add modal and functionality for adding providers --- actions/providers.ts | 3 - app/(prowler)/providers/page.tsx | 21 +--- components/index.ts | 2 + components/providers/AddProvider.tsx | 2 +- components/providers/AddProviderModal.tsx | 96 +++++++++++++++++ components/ui/dialog/Dialog.tsx | 122 +++++++++++++++++++++ config/fonts.ts | 2 + config/site.ts | 63 ----------- package-lock.json | 126 ++++++++++++++++++++++ package.json | 1 + 10 files changed, 352 insertions(+), 86 deletions(-) create mode 100644 components/providers/AddProviderModal.tsx create mode 100644 components/ui/dialog/Dialog.tsx diff --git a/actions/providers.ts b/actions/providers.ts index e339883d24..30ac54ff5b 100644 --- a/actions/providers.ts +++ b/actions/providers.ts @@ -20,7 +20,6 @@ export const getProvider = async () => { return undefined; } }; - export const addProvider = async (formData: FormData) => { const keyServer = process.env.LOCAL_SERVER_URL; @@ -57,7 +56,6 @@ export const addProvider = async (formData: FormData) => { } revalidatePath("/providers"); }; - export const checkConnectionProvider = async (formData: FormData) => { const keyServer = process.env.LOCAL_SERVER_URL; @@ -82,7 +80,6 @@ export const checkConnectionProvider = async (formData: FormData) => { }; } }; - export const deleteProvider = async (formData: FormData) => { const keyServer = process.env.LOCAL_SERVER_URL; diff --git a/app/(prowler)/providers/page.tsx b/app/(prowler)/providers/page.tsx index a6627e1cca..c8bee988f3 100644 --- a/app/(prowler)/providers/page.tsx +++ b/app/(prowler)/providers/page.tsx @@ -3,38 +3,21 @@ import React, { Suspense } from "react"; import { getProvider } from "@/actions"; import { - AddProvider, + AddProviderModal, ColumnsProviders, DataTable, Header, - ModalWrap, SkeletonTableProvider, } from "@/components"; export default async function Providers() { - const onSave = async () => { - "use server"; - // event we want to pass down, ex. console.log("### hello"); - }; - return ( <>
- - -

Modal body content

- - } - actionButtonLabel="Save" - onAction={onSave} - openButtonLabel="Add Cloud Accounts" - /> +
}> diff --git a/components/index.ts b/components/index.ts index a31ebbb99f..c28ecc331c 100644 --- a/components/index.ts +++ b/components/index.ts @@ -1,6 +1,7 @@ export * from "./findings/ColumnsFindings"; export * from "./findings/SkeletonTableFindings"; export * from "./providers/AddProvider"; +export * from "./providers/AddProviderModal"; export * from "./providers/ButtonAddProvider"; export * from "./providers/ButtonCheckConnectionProvider"; export * from "./providers/ButtonDeleteProvider"; @@ -12,6 +13,7 @@ export * from "./providers/table/ColumnsProviders"; export * from "./providers/table/DataTable"; export * from "./providers/table/SkeletonTableProvider"; export * from "./ui/alert/Alert"; +export * from "./ui/dialog/Dialog"; export * from "./ui/header/Header"; export * from "./ui/modal"; export * from "./ui/sidebar"; diff --git a/components/providers/AddProvider.tsx b/components/providers/AddProvider.tsx index ba914f01fd..5417f0ffe4 100644 --- a/components/providers/AddProvider.tsx +++ b/components/providers/AddProvider.tsx @@ -23,7 +23,7 @@ export const AddProvider = () => { toast({ variant: "destructive", title: "Ups! Something went wrong", - description: errorMessage + description: errorMessage, }); }); } else { diff --git a/components/providers/AddProviderModal.tsx b/components/providers/AddProviderModal.tsx new file mode 100644 index 0000000000..761b60ba2a --- /dev/null +++ b/components/providers/AddProviderModal.tsx @@ -0,0 +1,96 @@ +"use client"; + +import { Button, Input } from "@nextui-org/react"; +import { useRef, useState } from "react"; + +import { addProvider } from "@/actions"; +import { + ButtonAddProvider, + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogTrigger, + useToast, +} from "@/components"; + +export const AddProviderModal = () => { + const [open, setOpen] = useState(false); + + const ref = useRef(null); + const { toast } = useToast(); + + async function clientAction(formData: FormData) { + // reset the form + ref.current?.reset(); + // client-side validation + const data = await addProvider(formData); + if (data?.errors) { + data.errors.forEach((error: { detail: string }) => { + const errorMessage = `${error.detail}`; + // show error + toast({ + variant: "destructive", + title: "Ups! Something went wrong", + description: errorMessage, + }); + }); + } else { + toast({ + title: "Success!", + description: "The provider was added successfully.", + }); + } + } + return ( + + + + + + + + Add cloud account + + + You must manually deploy a new read-only IAM role for each account + you want to add. The following links will provide detailed + instructions how to do this: + + +
setOpen(false)} + className="flex flex-col gap-x-2" + > + + + + + +
+
+ ); +}; diff --git a/components/ui/dialog/Dialog.tsx b/components/ui/dialog/Dialog.tsx new file mode 100644 index 0000000000..a25a3caebc --- /dev/null +++ b/components/ui/dialog/Dialog.tsx @@ -0,0 +1,122 @@ +"use client"; + +import * as DialogPrimitive from "@radix-ui/react-dialog"; +import { X } from "lucide-react"; +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +const Dialog = DialogPrimitive.Root; + +const DialogTrigger = DialogPrimitive.Trigger; + +const DialogPortal = DialogPrimitive.Portal; + +const DialogClose = DialogPrimitive.Close; + +const DialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogOverlay.displayName = DialogPrimitive.Overlay.displayName; + +const DialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + {children} + + + Close + + + +)); +DialogContent.displayName = DialogPrimitive.Content.displayName; + +const DialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +DialogHeader.displayName = "DialogHeader"; + +const DialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +DialogFooter.displayName = "DialogFooter"; + +const DialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogTitle.displayName = DialogPrimitive.Title.displayName; + +const DialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogDescription.displayName = DialogPrimitive.Description.displayName; + +export { + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogOverlay, + DialogPortal, + DialogTitle, + DialogTrigger, +}; diff --git a/config/fonts.ts b/config/fonts.ts index 16c934d5a6..9724605392 100644 --- a/config/fonts.ts +++ b/config/fonts.ts @@ -6,9 +6,11 @@ import { export const fontSans = FontSans({ subsets: ["latin"], variable: "--font-sans", + preload: false, }); export const fontMono = FontMono({ subsets: ["latin"], variable: "--font-mono", + preload: false, }); diff --git a/config/site.ts b/config/site.ts index 23fcc45378..d89839859f 100644 --- a/config/site.ts +++ b/config/site.ts @@ -4,67 +4,4 @@ export const siteConfig = { name: "Prowler", description: "The most comprehensive, free tool for AWS security. ProwlerPro is trusted by leading organizations to make cloud security effortless.", - navItems: [ - { - label: "Home", - href: "/", - }, - { - label: "Docs", - href: "/docs", - }, - { - label: "Pricing", - href: "/pricing", - }, - { - label: "Blog", - href: "/blog", - }, - { - label: "About", - href: "/about", - }, - ], - navMenuItems: [ - { - label: "Profile", - href: "/profile", - }, - { - label: "Dashboard", - href: "/dashboard", - }, - { - label: "Projects", - href: "/projects", - }, - { - label: "Team", - href: "/team", - }, - { - label: "Calendar", - href: "/calendar", - }, - { - label: "Settings", - href: "/settings", - }, - { - label: "Help & Feedback", - href: "/help-feedback", - }, - { - label: "Logout", - href: "/logout", - }, - ], - links: { - github: "https://github.com/nextui-org/nextui", - twitter: "https://twitter.com/getnextui", - docs: "https://nextui.org", - discord: "https://discord.gg/9b6yyZKmH4", - sponsor: "https://patreon.com/jrgarciadev", - }, }; diff --git a/package-lock.json b/package-lock.json index fa6ecc0a45..7b8757dbd2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "@nextui-org/react": "^2.4.2", "@nextui-org/system": "2.2.1", "@nextui-org/theme": "2.2.5", + "@radix-ui/react-dialog": "^1.1.1", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-toast": "^1.2.1", "@react-aria/ssr": "3.9.4", @@ -2649,6 +2650,65 @@ } } }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.1.tgz", + "integrity": "sha512-zysS+iU4YP3STKNS6USvFVqI4qqx8EpiwmT5TuCApVEBca+eRCbONi4EgzfNSuVnOXvC5UPHHMjs8RXO6DH9Bg==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-dismissable-layer": "1.1.0", + "@radix-ui/react-focus-guards": "1.1.0", + "@radix-ui/react-focus-scope": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-portal": "1.1.1", + "@radix-ui/react-presence": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-slot": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.5.7" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/react-remove-scroll": { + "version": "2.5.7", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.7.tgz", + "integrity": "sha512-FnrTWO4L7/Bhhf3CYBNArEG/yROV0tKmTv7/3h9QCFvH6sndeFf1wPqOcbFVu5VAulS5dV1wGT3GZZ/1GawqiA==", + "dependencies": { + "react-remove-scroll-bar": "^2.3.4", + "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/@radix-ui/react-dismissable-layer": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.0.tgz", @@ -2675,6 +2735,44 @@ } } }, + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.0.tgz", + "integrity": "sha512-w6XZNUPVv6xCpZUqb/yN9DL6auvpGX3C/ee6Hdi16v2UUy25HV2Q5bcflsiDyT/g5RwbPQ/GIT1vLkeRb+ITBw==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.0.tgz", + "integrity": "sha512-200UD8zylvEyL8Bx+z76RJnASR2gRMuxlgFCPAe/Q/679a/r0eK3MBVYMb7vZODZcffZBdob1EGnky78xmVvcA==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-icons": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-icons/-/react-icons-1.3.0.tgz", @@ -2683,6 +2781,23 @@ "react": "^16.x || ^17.x || ^18.x" } }, + "node_modules/@radix-ui/react-id": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz", + "integrity": "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-portal": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.1.tgz", @@ -4478,6 +4593,17 @@ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, + "node_modules/aria-hidden": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.4.tgz", + "integrity": "sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/aria-query": { "version": "5.1.3", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", diff --git a/package.json b/package.json index d1837d47e3..7795424b1a 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "@nextui-org/react": "^2.4.2", "@nextui-org/system": "2.2.1", "@nextui-org/theme": "2.2.5", + "@radix-ui/react-dialog": "^1.1.1", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-toast": "^1.2.1", "@react-aria/ssr": "3.9.4", From 1992ef050aa5c0c456120eebe2d07534caaf47c4 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Mon, 5 Aug 2024 16:00:02 +0200 Subject: [PATCH 097/411] feat: refactor the modal's content for providers --- components/index.ts | 1 + components/providers/AddProviderModal.tsx | 53 +++++++------- components/providers/CustomRadioProvider.tsx | 77 ++++++++++++++++++++ 3 files changed, 106 insertions(+), 25 deletions(-) create mode 100644 components/providers/CustomRadioProvider.tsx diff --git a/components/index.ts b/components/index.ts index c28ecc331c..76c4ac2064 100644 --- a/components/index.ts +++ b/components/index.ts @@ -6,6 +6,7 @@ export * from "./providers/ButtonAddProvider"; export * from "./providers/ButtonCheckConnectionProvider"; export * from "./providers/ButtonDeleteProvider"; export * from "./providers/CheckConnectionProvider"; +export * from "./providers/CustomRadioProvider"; export * from "./providers/DateWithTime"; export * from "./providers/ProviderInfo"; export * from "./providers/ScanStatus"; diff --git a/components/providers/AddProviderModal.tsx b/components/providers/AddProviderModal.tsx index 761b60ba2a..878023ebb7 100644 --- a/components/providers/AddProviderModal.tsx +++ b/components/providers/AddProviderModal.tsx @@ -6,6 +6,7 @@ import { useRef, useState } from "react"; import { addProvider } from "@/actions"; import { ButtonAddProvider, + CustomRadioProvider, Dialog, DialogContent, DialogDescription, @@ -63,32 +64,34 @@ export const AddProviderModal = () => { ref={ref} action={clientAction} onSubmit={() => setOpen(false)} - className="flex flex-col gap-x-2" + className="grid sm:grid-cols-2 gap-6" > - - - - +
+ +
+
+ + +
+
+ +
diff --git a/components/providers/CustomRadioProvider.tsx b/components/providers/CustomRadioProvider.tsx new file mode 100644 index 0000000000..949cb86662 --- /dev/null +++ b/components/providers/CustomRadioProvider.tsx @@ -0,0 +1,77 @@ +"use client"; + +import { UseRadioProps } from "@nextui-org/radio/dist/use-radio"; +import { cn, RadioGroup, useRadio, VisuallyHidden } from "@nextui-org/react"; +import React from "react"; + +import { AwsProvider, AzureProvider, GoogleCloudProvider } from "../icons"; + +interface CustomRadioProps extends UseRadioProps { + description?: string; + children?: React.ReactNode; +} + +export const CustomRadio: React.FC = (props) => { + const { + Component, + children, + description, + getBaseProps, + getWrapperProps, + getInputProps, + getLabelProps, + getLabelWrapperProps, + getControlProps, + } = useRadio(props); + + return ( + + + + + + + +
+ {children && {children}} + {description && ( + + {description} + + )} +
+
+ ); +}; + +export const CustomRadioProvider = () => { + return ( + + +
+ + GCP +
+
+ +
+ + GCP +
+
+ +
+ + Azure +
+
+
+ ); +}; From 22ebe00cf61b1701723dfaa7c22fb61973f11b8c Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Tue, 6 Aug 2024 11:56:00 +0200 Subject: [PATCH 098/411] chore: adding a new index.ts file to improve the way we're exporting components (providers) --- app/(prowler)/findings/page.tsx | 8 ++------ app/(prowler)/providers/page.tsx | 6 +++--- components/index.ts | 13 ------------- components/providers/AddProviderModal.tsx | 5 +++-- components/providers/index.ts | 14 ++++++++++++++ 5 files changed, 22 insertions(+), 24 deletions(-) create mode 100644 components/providers/index.ts diff --git a/app/(prowler)/findings/page.tsx b/app/(prowler)/findings/page.tsx index 2595124a58..93a3e7f1f3 100644 --- a/app/(prowler)/findings/page.tsx +++ b/app/(prowler)/findings/page.tsx @@ -1,12 +1,8 @@ import { Spacer } from "@nextui-org/react"; import React, { Suspense } from "react"; -import { - ColumnsFindings, - DataTable, - Header, - SkeletonTableFindings, -} from "@/components"; +import { ColumnsFindings, Header, SkeletonTableFindings } from "@/components"; +import { DataTable } from "@/components/providers"; export default async function Findings() { return ( diff --git a/app/(prowler)/providers/page.tsx b/app/(prowler)/providers/page.tsx index c8bee988f3..9d6ae93758 100644 --- a/app/(prowler)/providers/page.tsx +++ b/app/(prowler)/providers/page.tsx @@ -1,14 +1,14 @@ import { Spacer } from "@nextui-org/react"; -import React, { Suspense } from "react"; +import { Suspense } from "react"; import { getProvider } from "@/actions"; +import { Header } from "@/components"; import { AddProviderModal, ColumnsProviders, DataTable, - Header, SkeletonTableProvider, -} from "@/components"; +} from "@/components/providers"; export default async function Providers() { return ( diff --git a/components/index.ts b/components/index.ts index 76c4ac2064..8035582871 100644 --- a/components/index.ts +++ b/components/index.ts @@ -1,18 +1,5 @@ export * from "./findings/ColumnsFindings"; export * from "./findings/SkeletonTableFindings"; -export * from "./providers/AddProvider"; -export * from "./providers/AddProviderModal"; -export * from "./providers/ButtonAddProvider"; -export * from "./providers/ButtonCheckConnectionProvider"; -export * from "./providers/ButtonDeleteProvider"; -export * from "./providers/CheckConnectionProvider"; -export * from "./providers/CustomRadioProvider"; -export * from "./providers/DateWithTime"; -export * from "./providers/ProviderInfo"; -export * from "./providers/ScanStatus"; -export * from "./providers/table/ColumnsProviders"; -export * from "./providers/table/DataTable"; -export * from "./providers/table/SkeletonTableProvider"; export * from "./ui/alert/Alert"; export * from "./ui/dialog/Dialog"; export * from "./ui/header/Header"; diff --git a/components/providers/AddProviderModal.tsx b/components/providers/AddProviderModal.tsx index 878023ebb7..51fa709a9b 100644 --- a/components/providers/AddProviderModal.tsx +++ b/components/providers/AddProviderModal.tsx @@ -5,8 +5,6 @@ import { useRef, useState } from "react"; import { addProvider } from "@/actions"; import { - ButtonAddProvider, - CustomRadioProvider, Dialog, DialogContent, DialogDescription, @@ -16,6 +14,9 @@ import { useToast, } from "@/components"; +import { ButtonAddProvider } from "./ButtonAddProvider"; +import { CustomRadioProvider } from "./CustomRadioProvider"; + export const AddProviderModal = () => { const [open, setOpen] = useState(false); diff --git a/components/providers/index.ts b/components/providers/index.ts new file mode 100644 index 0000000000..ea4406007a --- /dev/null +++ b/components/providers/index.ts @@ -0,0 +1,14 @@ +export * from "./AddProvider"; +export * from "./AddProviderModal"; +export * from "./ButtonAddProvider"; +export * from "./ButtonCheckConnectionProvider"; +export * from "./ButtonDeleteProvider"; +export * from "./CheckConnectionProvider"; +export * from "./CustomRadioProvider"; +export * from "./DateWithTime"; +export * from "./DeleteProvider"; +export * from "./ProviderInfo"; +export * from "./ScanStatus"; +export * from "./table/ColumnsProviders"; +export * from "./table/DataTable"; +export * from "./table/SkeletonTableProvider"; From 81bf3fc15ff6fa005bb43d4203e36dd0a39a738e Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Tue, 6 Aug 2024 12:05:10 +0200 Subject: [PATCH 099/411] chore: adding a new index.ts file to improve the way we're exporting components (ui) --- app/(prowler)/compliance/page.tsx | 2 +- app/(prowler)/error.tsx | 2 +- app/(prowler)/findings/page.tsx | 3 ++- app/(prowler)/integration/page.tsx | 2 +- app/(prowler)/layout.tsx | 2 +- app/(prowler)/page.tsx | 4 +--- app/(prowler)/providers/page.tsx | 2 +- app/(prowler)/services/page.tsx | 2 +- app/(prowler)/settings/page.tsx | 2 +- components/index.ts | 8 -------- components/providers/AddProviderModal.tsx | 2 +- components/ui/index.ts | 8 ++++++++ components/ui/modal/ModalWrap.tsx | 2 +- components/ui/toast/Toaster.tsx | 2 +- components/ui/toast/use-toast.ts | 2 +- 15 files changed, 22 insertions(+), 23 deletions(-) create mode 100644 components/ui/index.ts diff --git a/app/(prowler)/compliance/page.tsx b/app/(prowler)/compliance/page.tsx index 2c7a507043..59b39ea737 100644 --- a/app/(prowler)/compliance/page.tsx +++ b/app/(prowler)/compliance/page.tsx @@ -1,6 +1,6 @@ import React from "react"; -import { Header } from "@/components"; +import { Header } from "@/components/ui"; export default function Compliance() { return ( diff --git a/app/(prowler)/error.tsx b/app/(prowler)/error.tsx index fd8468f123..3719b239e3 100644 --- a/app/(prowler)/error.tsx +++ b/app/(prowler)/error.tsx @@ -3,8 +3,8 @@ import Link from "next/link"; import { useEffect } from "react"; -import { Alert, AlertDescription, AlertTitle } from "@/components"; import { RocketIcon } from "@/components/icons"; +import { Alert, AlertDescription, AlertTitle } from "@/components/ui"; export default function Error({ error, diff --git a/app/(prowler)/findings/page.tsx b/app/(prowler)/findings/page.tsx index 93a3e7f1f3..4a5e2f1947 100644 --- a/app/(prowler)/findings/page.tsx +++ b/app/(prowler)/findings/page.tsx @@ -1,8 +1,9 @@ import { Spacer } from "@nextui-org/react"; import React, { Suspense } from "react"; -import { ColumnsFindings, Header, SkeletonTableFindings } from "@/components"; +import { ColumnsFindings, SkeletonTableFindings } from "@/components"; import { DataTable } from "@/components/providers"; +import { Header } from "@/components/ui"; export default async function Findings() { return ( diff --git a/app/(prowler)/integration/page.tsx b/app/(prowler)/integration/page.tsx index 00e83f59ae..576089371b 100644 --- a/app/(prowler)/integration/page.tsx +++ b/app/(prowler)/integration/page.tsx @@ -1,6 +1,6 @@ import React from "react"; -import { Header } from "@/components"; +import { Header } from "@/components/ui"; export default function Integration() { return ( diff --git a/app/(prowler)/layout.tsx b/app/(prowler)/layout.tsx index cf932cfd20..ec533b5892 100644 --- a/app/(prowler)/layout.tsx +++ b/app/(prowler)/layout.tsx @@ -3,7 +3,7 @@ import "@/styles/globals.css"; import { Metadata, Viewport } from "next"; import React from "react"; -import { Toaster } from "@/components"; +import { Toaster } from "@/components/ui"; import { SidebarWrap } from "@/components/ui/sidebar"; import { fontSans } from "@/config/fonts"; import { siteConfig } from "@/config/site"; diff --git a/app/(prowler)/page.tsx b/app/(prowler)/page.tsx index 5d42aacbdb..3882fd7071 100644 --- a/app/(prowler)/page.tsx +++ b/app/(prowler)/page.tsx @@ -1,6 +1,4 @@ -import React from "react"; - -import { Header } from "@/components"; +import { Header } from "@/components/ui"; export default function Home() { return ( diff --git a/app/(prowler)/providers/page.tsx b/app/(prowler)/providers/page.tsx index 9d6ae93758..4653880538 100644 --- a/app/(prowler)/providers/page.tsx +++ b/app/(prowler)/providers/page.tsx @@ -2,13 +2,13 @@ import { Spacer } from "@nextui-org/react"; import { Suspense } from "react"; import { getProvider } from "@/actions"; -import { Header } from "@/components"; import { AddProviderModal, ColumnsProviders, DataTable, SkeletonTableProvider, } from "@/components/providers"; +import { Header } from "@/components/ui"; export default async function Providers() { return ( diff --git a/app/(prowler)/services/page.tsx b/app/(prowler)/services/page.tsx index 69cbf7c019..77c6de68ec 100644 --- a/app/(prowler)/services/page.tsx +++ b/app/(prowler)/services/page.tsx @@ -1,6 +1,6 @@ import React from "react"; -import { Header } from "@/components"; +import { Header } from "@/components/ui"; export default function Services() { return ( diff --git a/app/(prowler)/settings/page.tsx b/app/(prowler)/settings/page.tsx index 526b8c08df..6653c38b1c 100644 --- a/app/(prowler)/settings/page.tsx +++ b/app/(prowler)/settings/page.tsx @@ -1,6 +1,6 @@ import { Spacer } from "@nextui-org/react"; -import { Header } from "@/components"; +import { Header } from "@/components/ui"; export default async function Settings() { return ( diff --git a/components/index.ts b/components/index.ts index 8035582871..df78769815 100644 --- a/components/index.ts +++ b/components/index.ts @@ -1,10 +1,2 @@ export * from "./findings/ColumnsFindings"; export * from "./findings/SkeletonTableFindings"; -export * from "./ui/alert/Alert"; -export * from "./ui/dialog/Dialog"; -export * from "./ui/header/Header"; -export * from "./ui/modal"; -export * from "./ui/sidebar"; -export * from "./ui/table/StatusBadge"; -export * from "./ui/table/Table"; -export * from "./ui/toast"; diff --git a/components/providers/AddProviderModal.tsx b/components/providers/AddProviderModal.tsx index 51fa709a9b..a04c19c7bb 100644 --- a/components/providers/AddProviderModal.tsx +++ b/components/providers/AddProviderModal.tsx @@ -12,7 +12,7 @@ import { DialogTitle, DialogTrigger, useToast, -} from "@/components"; +} from "@/components/ui"; import { ButtonAddProvider } from "./ButtonAddProvider"; import { CustomRadioProvider } from "./CustomRadioProvider"; diff --git a/components/ui/index.ts b/components/ui/index.ts new file mode 100644 index 0000000000..da4f2bfeaf --- /dev/null +++ b/components/ui/index.ts @@ -0,0 +1,8 @@ +export * from "./alert/Alert"; +export * from "./dialog/Dialog"; +export * from "./header/Header"; +export * from "./modal"; +export * from "./sidebar"; +export * from "./table/StatusBadge"; +export * from "./table/Table"; +export * from "./toast"; diff --git a/components/ui/modal/ModalWrap.tsx b/components/ui/modal/ModalWrap.tsx index 42318210bf..15c4be4133 100644 --- a/components/ui/modal/ModalWrap.tsx +++ b/components/ui/modal/ModalWrap.tsx @@ -3,7 +3,7 @@ import { Button, useDisclosure } from "@nextui-org/react"; import React from "react"; -import { Modal } from "@/components"; +import { Modal } from "@/components/ui"; interface ModalWrapProps { modalTitle: string; diff --git a/components/ui/toast/Toaster.tsx b/components/ui/toast/Toaster.tsx index f264ad448b..4287ed5c8b 100644 --- a/components/ui/toast/Toaster.tsx +++ b/components/ui/toast/Toaster.tsx @@ -7,7 +7,7 @@ import { ToastProvider, ToastTitle, ToastViewport, -} from "@/components"; +} from "@/components/ui"; import { useToast } from "./use-toast"; diff --git a/components/ui/toast/use-toast.ts b/components/ui/toast/use-toast.ts index 1a7c514a81..26eda1f3e1 100644 --- a/components/ui/toast/use-toast.ts +++ b/components/ui/toast/use-toast.ts @@ -3,7 +3,7 @@ // Inspired by react-hot-toast library import * as React from "react"; -import type { ToastActionElement, ToastProps } from "@/components"; +import type { ToastActionElement, ToastProps } from "@/components/ui"; const TOAST_LIMIT = 6; const TOAST_REMOVE_DELAY = 1000000; From b7d324f1b0789d4f52c2fff364525bd8f91530fe Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Mon, 5 Aug 2024 13:09:44 +0200 Subject: [PATCH 100/411] feat: add modal and functionality for adding providers --- actions/providers.ts | 3 - app/(prowler)/providers/page.tsx | 21 +--- components/index.ts | 2 + components/providers/AddProvider.tsx | 2 +- components/providers/AddProviderModal.tsx | 96 +++++++++++++++++ components/ui/dialog/Dialog.tsx | 122 +++++++++++++++++++++ config/fonts.ts | 2 + config/site.ts | 63 ----------- package-lock.json | 126 ++++++++++++++++++++++ package.json | 1 + 10 files changed, 352 insertions(+), 86 deletions(-) create mode 100644 components/providers/AddProviderModal.tsx create mode 100644 components/ui/dialog/Dialog.tsx diff --git a/actions/providers.ts b/actions/providers.ts index e339883d24..30ac54ff5b 100644 --- a/actions/providers.ts +++ b/actions/providers.ts @@ -20,7 +20,6 @@ export const getProvider = async () => { return undefined; } }; - export const addProvider = async (formData: FormData) => { const keyServer = process.env.LOCAL_SERVER_URL; @@ -57,7 +56,6 @@ export const addProvider = async (formData: FormData) => { } revalidatePath("/providers"); }; - export const checkConnectionProvider = async (formData: FormData) => { const keyServer = process.env.LOCAL_SERVER_URL; @@ -82,7 +80,6 @@ export const checkConnectionProvider = async (formData: FormData) => { }; } }; - export const deleteProvider = async (formData: FormData) => { const keyServer = process.env.LOCAL_SERVER_URL; diff --git a/app/(prowler)/providers/page.tsx b/app/(prowler)/providers/page.tsx index a6627e1cca..c8bee988f3 100644 --- a/app/(prowler)/providers/page.tsx +++ b/app/(prowler)/providers/page.tsx @@ -3,38 +3,21 @@ import React, { Suspense } from "react"; import { getProvider } from "@/actions"; import { - AddProvider, + AddProviderModal, ColumnsProviders, DataTable, Header, - ModalWrap, SkeletonTableProvider, } from "@/components"; export default async function Providers() { - const onSave = async () => { - "use server"; - // event we want to pass down, ex. console.log("### hello"); - }; - return ( <>
- - -

Modal body content

- - } - actionButtonLabel="Save" - onAction={onSave} - openButtonLabel="Add Cloud Accounts" - /> +
}> diff --git a/components/index.ts b/components/index.ts index a31ebbb99f..c28ecc331c 100644 --- a/components/index.ts +++ b/components/index.ts @@ -1,6 +1,7 @@ export * from "./findings/ColumnsFindings"; export * from "./findings/SkeletonTableFindings"; export * from "./providers/AddProvider"; +export * from "./providers/AddProviderModal"; export * from "./providers/ButtonAddProvider"; export * from "./providers/ButtonCheckConnectionProvider"; export * from "./providers/ButtonDeleteProvider"; @@ -12,6 +13,7 @@ export * from "./providers/table/ColumnsProviders"; export * from "./providers/table/DataTable"; export * from "./providers/table/SkeletonTableProvider"; export * from "./ui/alert/Alert"; +export * from "./ui/dialog/Dialog"; export * from "./ui/header/Header"; export * from "./ui/modal"; export * from "./ui/sidebar"; diff --git a/components/providers/AddProvider.tsx b/components/providers/AddProvider.tsx index ba914f01fd..5417f0ffe4 100644 --- a/components/providers/AddProvider.tsx +++ b/components/providers/AddProvider.tsx @@ -23,7 +23,7 @@ export const AddProvider = () => { toast({ variant: "destructive", title: "Ups! Something went wrong", - description: errorMessage + description: errorMessage, }); }); } else { diff --git a/components/providers/AddProviderModal.tsx b/components/providers/AddProviderModal.tsx new file mode 100644 index 0000000000..761b60ba2a --- /dev/null +++ b/components/providers/AddProviderModal.tsx @@ -0,0 +1,96 @@ +"use client"; + +import { Button, Input } from "@nextui-org/react"; +import { useRef, useState } from "react"; + +import { addProvider } from "@/actions"; +import { + ButtonAddProvider, + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogTrigger, + useToast, +} from "@/components"; + +export const AddProviderModal = () => { + const [open, setOpen] = useState(false); + + const ref = useRef(null); + const { toast } = useToast(); + + async function clientAction(formData: FormData) { + // reset the form + ref.current?.reset(); + // client-side validation + const data = await addProvider(formData); + if (data?.errors) { + data.errors.forEach((error: { detail: string }) => { + const errorMessage = `${error.detail}`; + // show error + toast({ + variant: "destructive", + title: "Ups! Something went wrong", + description: errorMessage, + }); + }); + } else { + toast({ + title: "Success!", + description: "The provider was added successfully.", + }); + } + } + return ( + + + + + + + + Add cloud account + + + You must manually deploy a new read-only IAM role for each account + you want to add. The following links will provide detailed + instructions how to do this: + + +
setOpen(false)} + className="flex flex-col gap-x-2" + > + + + + + +
+
+ ); +}; diff --git a/components/ui/dialog/Dialog.tsx b/components/ui/dialog/Dialog.tsx new file mode 100644 index 0000000000..a25a3caebc --- /dev/null +++ b/components/ui/dialog/Dialog.tsx @@ -0,0 +1,122 @@ +"use client"; + +import * as DialogPrimitive from "@radix-ui/react-dialog"; +import { X } from "lucide-react"; +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +const Dialog = DialogPrimitive.Root; + +const DialogTrigger = DialogPrimitive.Trigger; + +const DialogPortal = DialogPrimitive.Portal; + +const DialogClose = DialogPrimitive.Close; + +const DialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogOverlay.displayName = DialogPrimitive.Overlay.displayName; + +const DialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + {children} + + + Close + + + +)); +DialogContent.displayName = DialogPrimitive.Content.displayName; + +const DialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +DialogHeader.displayName = "DialogHeader"; + +const DialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +DialogFooter.displayName = "DialogFooter"; + +const DialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogTitle.displayName = DialogPrimitive.Title.displayName; + +const DialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogDescription.displayName = DialogPrimitive.Description.displayName; + +export { + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogOverlay, + DialogPortal, + DialogTitle, + DialogTrigger, +}; diff --git a/config/fonts.ts b/config/fonts.ts index 16c934d5a6..9724605392 100644 --- a/config/fonts.ts +++ b/config/fonts.ts @@ -6,9 +6,11 @@ import { export const fontSans = FontSans({ subsets: ["latin"], variable: "--font-sans", + preload: false, }); export const fontMono = FontMono({ subsets: ["latin"], variable: "--font-mono", + preload: false, }); diff --git a/config/site.ts b/config/site.ts index 23fcc45378..d89839859f 100644 --- a/config/site.ts +++ b/config/site.ts @@ -4,67 +4,4 @@ export const siteConfig = { name: "Prowler", description: "The most comprehensive, free tool for AWS security. ProwlerPro is trusted by leading organizations to make cloud security effortless.", - navItems: [ - { - label: "Home", - href: "/", - }, - { - label: "Docs", - href: "/docs", - }, - { - label: "Pricing", - href: "/pricing", - }, - { - label: "Blog", - href: "/blog", - }, - { - label: "About", - href: "/about", - }, - ], - navMenuItems: [ - { - label: "Profile", - href: "/profile", - }, - { - label: "Dashboard", - href: "/dashboard", - }, - { - label: "Projects", - href: "/projects", - }, - { - label: "Team", - href: "/team", - }, - { - label: "Calendar", - href: "/calendar", - }, - { - label: "Settings", - href: "/settings", - }, - { - label: "Help & Feedback", - href: "/help-feedback", - }, - { - label: "Logout", - href: "/logout", - }, - ], - links: { - github: "https://github.com/nextui-org/nextui", - twitter: "https://twitter.com/getnextui", - docs: "https://nextui.org", - discord: "https://discord.gg/9b6yyZKmH4", - sponsor: "https://patreon.com/jrgarciadev", - }, }; diff --git a/package-lock.json b/package-lock.json index fa6ecc0a45..7b8757dbd2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "@nextui-org/react": "^2.4.2", "@nextui-org/system": "2.2.1", "@nextui-org/theme": "2.2.5", + "@radix-ui/react-dialog": "^1.1.1", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-toast": "^1.2.1", "@react-aria/ssr": "3.9.4", @@ -2649,6 +2650,65 @@ } } }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.1.tgz", + "integrity": "sha512-zysS+iU4YP3STKNS6USvFVqI4qqx8EpiwmT5TuCApVEBca+eRCbONi4EgzfNSuVnOXvC5UPHHMjs8RXO6DH9Bg==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-dismissable-layer": "1.1.0", + "@radix-ui/react-focus-guards": "1.1.0", + "@radix-ui/react-focus-scope": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-portal": "1.1.1", + "@radix-ui/react-presence": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-slot": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.5.7" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/react-remove-scroll": { + "version": "2.5.7", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.7.tgz", + "integrity": "sha512-FnrTWO4L7/Bhhf3CYBNArEG/yROV0tKmTv7/3h9QCFvH6sndeFf1wPqOcbFVu5VAulS5dV1wGT3GZZ/1GawqiA==", + "dependencies": { + "react-remove-scroll-bar": "^2.3.4", + "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/@radix-ui/react-dismissable-layer": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.0.tgz", @@ -2675,6 +2735,44 @@ } } }, + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.0.tgz", + "integrity": "sha512-w6XZNUPVv6xCpZUqb/yN9DL6auvpGX3C/ee6Hdi16v2UUy25HV2Q5bcflsiDyT/g5RwbPQ/GIT1vLkeRb+ITBw==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.0.tgz", + "integrity": "sha512-200UD8zylvEyL8Bx+z76RJnASR2gRMuxlgFCPAe/Q/679a/r0eK3MBVYMb7vZODZcffZBdob1EGnky78xmVvcA==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-icons": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-icons/-/react-icons-1.3.0.tgz", @@ -2683,6 +2781,23 @@ "react": "^16.x || ^17.x || ^18.x" } }, + "node_modules/@radix-ui/react-id": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz", + "integrity": "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-portal": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.1.tgz", @@ -4478,6 +4593,17 @@ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, + "node_modules/aria-hidden": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.4.tgz", + "integrity": "sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/aria-query": { "version": "5.1.3", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", diff --git a/package.json b/package.json index d1837d47e3..7795424b1a 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "@nextui-org/react": "^2.4.2", "@nextui-org/system": "2.2.1", "@nextui-org/theme": "2.2.5", + "@radix-ui/react-dialog": "^1.1.1", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-toast": "^1.2.1", "@react-aria/ssr": "3.9.4", From 14ff4282c0158933a3a72c406bd59d2ab668a35e Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Mon, 5 Aug 2024 16:00:02 +0200 Subject: [PATCH 101/411] feat: refactor the modal's content for providers --- components/index.ts | 1 + components/providers/AddProviderModal.tsx | 53 +++++++------- components/providers/CustomRadioProvider.tsx | 77 ++++++++++++++++++++ 3 files changed, 106 insertions(+), 25 deletions(-) create mode 100644 components/providers/CustomRadioProvider.tsx diff --git a/components/index.ts b/components/index.ts index c28ecc331c..76c4ac2064 100644 --- a/components/index.ts +++ b/components/index.ts @@ -6,6 +6,7 @@ export * from "./providers/ButtonAddProvider"; export * from "./providers/ButtonCheckConnectionProvider"; export * from "./providers/ButtonDeleteProvider"; export * from "./providers/CheckConnectionProvider"; +export * from "./providers/CustomRadioProvider"; export * from "./providers/DateWithTime"; export * from "./providers/ProviderInfo"; export * from "./providers/ScanStatus"; diff --git a/components/providers/AddProviderModal.tsx b/components/providers/AddProviderModal.tsx index 761b60ba2a..878023ebb7 100644 --- a/components/providers/AddProviderModal.tsx +++ b/components/providers/AddProviderModal.tsx @@ -6,6 +6,7 @@ import { useRef, useState } from "react"; import { addProvider } from "@/actions"; import { ButtonAddProvider, + CustomRadioProvider, Dialog, DialogContent, DialogDescription, @@ -63,32 +64,34 @@ export const AddProviderModal = () => { ref={ref} action={clientAction} onSubmit={() => setOpen(false)} - className="flex flex-col gap-x-2" + className="grid sm:grid-cols-2 gap-6" > - - - - +
+ +
+
+ + +
+
+ +
diff --git a/components/providers/CustomRadioProvider.tsx b/components/providers/CustomRadioProvider.tsx new file mode 100644 index 0000000000..949cb86662 --- /dev/null +++ b/components/providers/CustomRadioProvider.tsx @@ -0,0 +1,77 @@ +"use client"; + +import { UseRadioProps } from "@nextui-org/radio/dist/use-radio"; +import { cn, RadioGroup, useRadio, VisuallyHidden } from "@nextui-org/react"; +import React from "react"; + +import { AwsProvider, AzureProvider, GoogleCloudProvider } from "../icons"; + +interface CustomRadioProps extends UseRadioProps { + description?: string; + children?: React.ReactNode; +} + +export const CustomRadio: React.FC = (props) => { + const { + Component, + children, + description, + getBaseProps, + getWrapperProps, + getInputProps, + getLabelProps, + getLabelWrapperProps, + getControlProps, + } = useRadio(props); + + return ( + + + + + + + +
+ {children && {children}} + {description && ( + + {description} + + )} +
+
+ ); +}; + +export const CustomRadioProvider = () => { + return ( + + +
+ + GCP +
+
+ +
+ + GCP +
+
+ +
+ + Azure +
+
+
+ ); +}; From 4137eaec6dbf2b4634ada3c8583e8f43d3c199dd Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Tue, 6 Aug 2024 17:24:18 +0200 Subject: [PATCH 102/411] chore: fix typo --- app/(prowler)/providers/page.tsx | 4 ++-- components/providers/ButtonDeleteProvider.tsx | 2 +- components/providers/index.ts | 2 +- .../table/{ColumnsProviders.tsx => ColumnsProvider.tsx} | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) rename components/providers/table/{ColumnsProviders.tsx => ColumnsProvider.tsx} (98%) diff --git a/app/(prowler)/providers/page.tsx b/app/(prowler)/providers/page.tsx index 4653880538..e12a3027ab 100644 --- a/app/(prowler)/providers/page.tsx +++ b/app/(prowler)/providers/page.tsx @@ -4,7 +4,7 @@ import { Suspense } from "react"; import { getProvider } from "@/actions"; import { AddProviderModal, - ColumnsProviders, + ColumnsProvider, DataTable, SkeletonTableProvider, } from "@/components/providers"; @@ -31,5 +31,5 @@ export default async function Providers() { const SSRDataTable = async () => { const providersData = await getProvider(); const [providers] = await Promise.all([providersData]); - return ; + return ; }; diff --git a/components/providers/ButtonDeleteProvider.tsx b/components/providers/ButtonDeleteProvider.tsx index 797e1a5022..ef13a5c9e5 100644 --- a/components/providers/ButtonDeleteProvider.tsx +++ b/components/providers/ButtonDeleteProvider.tsx @@ -11,7 +11,7 @@ export const ButtonDeleteProvider = () => { return ( - - - - )} - - - - ); -}; diff --git a/components/ui/modal/ModalWrap.tsx b/components/ui/modal/ModalWrap.tsx deleted file mode 100644 index 15c4be4133..0000000000 --- a/components/ui/modal/ModalWrap.tsx +++ /dev/null @@ -1,48 +0,0 @@ -"use client"; - -import { Button, useDisclosure } from "@nextui-org/react"; -import React from "react"; - -import { Modal } from "@/components/ui"; - -interface ModalWrapProps { - modalTitle: string; - modalBody: React.ReactNode; - closeButtonLabel?: string; - actionButtonLabel?: string; - onAction: () => void; - openButtonLabel?: string; - isDismissable?: boolean; -} - -export const ModalWrap: React.FC = ({ - modalTitle, - modalBody, - closeButtonLabel = "Close", - actionButtonLabel = "Save", - onAction, - openButtonLabel = "Open", - isDismissable = true, -}) => { - const { isOpen, onOpen, onClose, onOpenChange } = useDisclosure(); - const closeOnAction = () => { - onAction?.(); - onClose(); - }; - - return ( - <> - - - - ); -}; diff --git a/components/ui/modal/index.ts b/components/ui/modal/index.ts deleted file mode 100644 index c33df2971c..0000000000 --- a/components/ui/modal/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./Modal"; -export * from "./ModalWrap"; From 4483baae19384a3dcbbaa6d0fa9a01df598392f2 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Wed, 7 Aug 2024 09:14:17 +0200 Subject: [PATCH 105/411] chore: rename DataTable to DataTableProvider for more specificity --- app/(prowler)/findings/page.tsx | 4 ++-- app/(prowler)/providers/page.tsx | 6 ++++-- components/providers/index.ts | 2 +- .../table/{DataTable.tsx => DataTableProvider.tsx} | 2 +- 4 files changed, 8 insertions(+), 6 deletions(-) rename components/providers/table/{DataTable.tsx => DataTableProvider.tsx} (98%) diff --git a/app/(prowler)/findings/page.tsx b/app/(prowler)/findings/page.tsx index 4a5e2f1947..33a8746191 100644 --- a/app/(prowler)/findings/page.tsx +++ b/app/(prowler)/findings/page.tsx @@ -2,7 +2,7 @@ import { Spacer } from "@nextui-org/react"; import React, { Suspense } from "react"; import { ColumnsFindings, SkeletonTableFindings } from "@/components"; -import { DataTable } from "@/components/providers"; +import { DataTableProvider } from "@/components/providers"; import { Header } from "@/components/ui"; export default async function Findings() { @@ -22,7 +22,7 @@ export default async function Findings() { const SSRDataTable = async () => { return ( - { const providersData = await getProvider(); const [providers] = await Promise.all([providersData]); - return ; + return ( + + ); }; diff --git a/components/providers/index.ts b/components/providers/index.ts index dfe20bca77..00bd2634c5 100644 --- a/components/providers/index.ts +++ b/components/providers/index.ts @@ -10,5 +10,5 @@ export * from "./DeleteProvider"; export * from "./ProviderInfo"; export * from "./ScanStatus"; export * from "./table/ColumnsProvider"; -export * from "./table/DataTable"; +export * from "./table/DataTableProvider"; export * from "./table/SkeletonTableProvider"; diff --git a/components/providers/table/DataTable.tsx b/components/providers/table/DataTableProvider.tsx similarity index 98% rename from components/providers/table/DataTable.tsx rename to components/providers/table/DataTableProvider.tsx index cfcf74ff2a..a24202685e 100644 --- a/components/providers/table/DataTable.tsx +++ b/components/providers/table/DataTableProvider.tsx @@ -23,7 +23,7 @@ interface DataTableProps { data: TData[]; } -export function DataTable({ +export function DataTableProvider({ columns, data, }: DataTableProps) { From ca3d076607eaa9291eb7c801a946ef9a4587dca2 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Wed, 7 Aug 2024 13:56:36 +0200 Subject: [PATCH 106/411] feat: add Pagination component and DataTableColumnHeader component --- components/providers/index.ts | 2 + .../providers/table/DataTableColumnHeader.tsx | 49 ++++ .../providers/table/DataTablePagination.tsx | 66 +++++ .../providers/table/DataTableProvider.tsx | 31 ++- components/ui/index.ts | 1 + components/ui/select/Select.tsx | 164 +++++++++++++ package-lock.json | 226 ++++++++++++++++++ package.json | 1 + 8 files changed, 523 insertions(+), 17 deletions(-) create mode 100644 components/providers/table/DataTableColumnHeader.tsx create mode 100644 components/providers/table/DataTablePagination.tsx create mode 100644 components/ui/select/Select.tsx diff --git a/components/providers/index.ts b/components/providers/index.ts index 00bd2634c5..0b4074236c 100644 --- a/components/providers/index.ts +++ b/components/providers/index.ts @@ -10,5 +10,7 @@ export * from "./DeleteProvider"; export * from "./ProviderInfo"; export * from "./ScanStatus"; export * from "./table/ColumnsProvider"; +export * from "./table/DataTableColumnHeader"; +export * from "./table/DataTablePagination"; export * from "./table/DataTableProvider"; export * from "./table/SkeletonTableProvider"; diff --git a/components/providers/table/DataTableColumnHeader.tsx b/components/providers/table/DataTableColumnHeader.tsx new file mode 100644 index 0000000000..7fe6027527 --- /dev/null +++ b/components/providers/table/DataTableColumnHeader.tsx @@ -0,0 +1,49 @@ +import { Button } from "@nextui-org/react"; +import { Column } from "@tanstack/react-table"; +import { + ArrowDownIcon, + ArrowUpIcon, + ChevronsLeftRightIcon, +} from "lucide-react"; +import { HTMLAttributes } from "react"; + +interface DataTableColumnHeaderProps + extends HTMLAttributes { + column: Column; + title: string; +} + +export const DataTableColumnHeader = ({ + column, + title, + className, +}: DataTableColumnHeaderProps) => { + const renderSortIcon = () => { + const sort = column.getIsSorted(); + if (!sort) { + return ; + } + return sort === "desc" ? ( + + ) : ( + + ); + }; + + if (!column.getCanSort()) { + return
{title}
; + } + return ( +
+ +
+ ); +}; diff --git a/components/providers/table/DataTablePagination.tsx b/components/providers/table/DataTablePagination.tsx new file mode 100644 index 0000000000..6f31f826e6 --- /dev/null +++ b/components/providers/table/DataTablePagination.tsx @@ -0,0 +1,66 @@ +import { Button } from "@nextui-org/react"; +import { + ChevronLeftIcon, + ChevronRightIcon, + DoubleArrowLeftIcon, + DoubleArrowRightIcon, +} from "@radix-ui/react-icons"; +import { type Table } from "@tanstack/react-table"; + +interface DataTablePaginationProps { + table: Table; + pageSizeOptions?: number[]; +} + +export function DataTablePagination({ + table, +}: DataTablePaginationProps) { + return ( +
+
+
+ Page {table.getState().pagination.pageIndex + 1} of{" "} + {table.getPageCount()} +
+
+ + + + +
+
+
+ ); +} diff --git a/components/providers/table/DataTableProvider.tsx b/components/providers/table/DataTableProvider.tsx index a24202685e..a6ddca5d65 100644 --- a/components/providers/table/DataTableProvider.tsx +++ b/components/providers/table/DataTableProvider.tsx @@ -1,13 +1,15 @@ "use client"; -import { Button } from "@nextui-org/react"; import { ColumnDef, flexRender, getCoreRowModel, getPaginationRowModel, + getSortedRowModel, + SortingState, useReactTable, } from "@tanstack/react-table"; +import { useState } from "react"; import { Table, @@ -18,6 +20,8 @@ import { TableRow, } from "@/components/ui/table/Table"; +import { DataTablePagination } from "./DataTablePagination"; + interface DataTableProps { columns: ColumnDef[]; data: TData[]; @@ -27,11 +31,19 @@ export function DataTableProvider({ columns, data, }: DataTableProps) { + const [sorting, setSorting] = useState([]); + const table = useReactTable({ data, columns, + enableSorting: true, getCoreRowModel: getCoreRowModel(), getPaginationRowModel: getPaginationRowModel(), + onSortingChange: setSorting, + getSortedRowModel: getSortedRowModel(), + state: { + sorting, + }, }); return ( @@ -87,22 +99,7 @@ export function DataTableProvider({
- - +
); diff --git a/components/ui/index.ts b/components/ui/index.ts index 96064508d6..f202f8ffe5 100644 --- a/components/ui/index.ts +++ b/components/ui/index.ts @@ -1,6 +1,7 @@ export * from "./alert/Alert"; export * from "./dialog/Dialog"; export * from "./header/Header"; +export * from "./select/Select"; export * from "./sidebar"; export * from "./table/StatusBadge"; export * from "./table/Table"; diff --git a/components/ui/select/Select.tsx b/components/ui/select/Select.tsx new file mode 100644 index 0000000000..64b4d471c5 --- /dev/null +++ b/components/ui/select/Select.tsx @@ -0,0 +1,164 @@ +"use client"; + +import { + CaretSortIcon, + CheckIcon, + ChevronDownIcon, + ChevronUpIcon, +} from "@radix-ui/react-icons"; +import * as SelectPrimitive from "@radix-ui/react-select"; +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +const Select = SelectPrimitive.Root; + +const SelectGroup = SelectPrimitive.Group; + +const SelectValue = SelectPrimitive.Value; + +const SelectTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + span]:line-clamp-1 dark:border-slate-800 dark:ring-offset-slate-950 dark:placeholder:text-slate-400 dark:focus:ring-slate-300", + className, + )} + {...props} + > + {children} + + + + +)); +SelectTrigger.displayName = SelectPrimitive.Trigger.displayName; + +const SelectScrollUpButton = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)); +SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName; + +const SelectScrollDownButton = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)); +SelectScrollDownButton.displayName = + SelectPrimitive.ScrollDownButton.displayName; + +const SelectContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, position = "popper", ...props }, ref) => ( + + + + + {children} + + + + +)); +SelectContent.displayName = SelectPrimitive.Content.displayName; + +const SelectLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +SelectLabel.displayName = SelectPrimitive.Label.displayName; + +const SelectItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + + + + {children} + +)); +SelectItem.displayName = SelectPrimitive.Item.displayName; + +const SelectSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +SelectSeparator.displayName = SelectPrimitive.Separator.displayName; + +export { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectLabel, + SelectScrollDownButton, + SelectScrollUpButton, + SelectSeparator, + SelectTrigger, + SelectValue, +}; diff --git a/package-lock.json b/package-lock.json index 7b8757dbd2..3ef31ce458 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@nextui-org/theme": "2.2.5", "@radix-ui/react-dialog": "^1.1.1", "@radix-ui/react-icons": "^1.3.0", + "@radix-ui/react-select": "^2.1.1", "@radix-ui/react-toast": "^1.2.1", "@react-aria/ssr": "3.9.4", "@react-aria/visually-hidden": "3.8.12", @@ -649,6 +650,40 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@floating-ui/core": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.6.tgz", + "integrity": "sha512-Vkvsw6EcpMHjvZZdMkSY+djMGFbt7CRssW99Ne8tar2WLnZ/l3dbxeTShbLQj+/s35h+Qb4cmnob+EzwtjrXGQ==", + "dependencies": { + "@floating-ui/utils": "^0.2.6" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.6.9", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.9.tgz", + "integrity": "sha512-zB1PcI350t4tkm3rvUhSRKa9sT7vH5CrAbQxW+VaPYJXKAO0gsg4CTueL+6Ajp7XzAQC8CW4Jj1Wgqc0sB6oUQ==", + "dependencies": { + "@floating-ui/core": "^1.6.0", + "@floating-ui/utils": "^0.2.6" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.1.tgz", + "integrity": "sha512-4h84MJt3CHrtG18mGsXuLCHMrug49d7DFkU0RMIyshRveBeyV2hmV/pDaF2Uxtu8kgq5r46llp5E5FQiR0K2Yg==", + "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.6", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.6.tgz", + "integrity": "sha512-0KI3zGxIUs1KDR/pjQPdJH4Z8nGBm0yJ5WRoRfdw1Kzeh45jkIfA0rmD0kBF6fKHH+xaH7g8y4jIXyAV5MGK3g==" + }, "node_modules/@formatjs/ecma402-abstract": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.0.0.tgz", @@ -2592,11 +2627,38 @@ "url": "https://opencollective.com/unts" } }, + "node_modules/@radix-ui/number": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.0.tgz", + "integrity": "sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ==" + }, "node_modules/@radix-ui/primitive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz", "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==" }, + "node_modules/@radix-ui/react-arrow": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.0.tgz", + "integrity": "sha512-FmlW1rCg7hBpEBwFbjHwCW6AmWLQM6g/v0Sn8XbP9NvmSZ2San1FpQeyPtufzOMSIx7Y4dzjlHoifhp+7NkZhw==", + "dependencies": { + "@radix-ui/react-primitive": "2.0.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-collection": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.0.tgz", @@ -2709,6 +2771,20 @@ } } }, + "node_modules/@radix-ui/react-direction": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.0.tgz", + "integrity": "sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-dismissable-layer": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.0.tgz", @@ -2798,6 +2874,37 @@ } } }, + "node_modules/@radix-ui/react-popper": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.0.tgz", + "integrity": "sha512-ZnRMshKF43aBxVWPWvbj21+7TQCvhuULWJ4gNIKYpRlQt5xGRhLx66tMp8pya2UkGHTSlhpXwmjqltDYHhw7Vg==", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0", + "@radix-ui/react-use-rect": "1.1.0", + "@radix-ui/react-use-size": "1.1.0", + "@radix-ui/rect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-portal": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.1.tgz", @@ -2866,6 +2973,72 @@ } } }, + "node_modules/@radix-ui/react-select": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.1.1.tgz", + "integrity": "sha512-8iRDfyLtzxlprOo9IicnzvpsO1wNCkuwzzCM+Z5Rb5tNOpCdMvcc2AkzX0Fz+Tz9v6NJ5B/7EEgyZveo4FBRfQ==", + "dependencies": { + "@radix-ui/number": "1.1.0", + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-collection": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-dismissable-layer": "1.1.0", + "@radix-ui/react-focus-guards": "1.1.0", + "@radix-ui/react-focus-scope": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-popper": "1.2.0", + "@radix-ui/react-portal": "1.1.1", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-slot": "1.1.0", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0", + "@radix-ui/react-use-previous": "1.1.0", + "@radix-ui/react-visually-hidden": "1.1.0", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.5.7" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/react-remove-scroll": { + "version": "2.5.7", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.7.tgz", + "integrity": "sha512-FnrTWO4L7/Bhhf3CYBNArEG/yROV0tKmTv7/3h9QCFvH6sndeFf1wPqOcbFVu5VAulS5dV1wGT3GZZ/1GawqiA==", + "dependencies": { + "react-remove-scroll-bar": "^2.3.4", + "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/@radix-ui/react-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz", @@ -2978,6 +3151,54 @@ } } }, + "node_modules/@radix-ui/react-use-previous": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.0.tgz", + "integrity": "sha512-Z/e78qg2YFnnXcW88A4JmTtm4ADckLno6F7OXotmkQfeuCVaKuYzqAATPhVzl3delXE7CxIV8shofPn3jPc5Og==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-rect": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.0.tgz", + "integrity": "sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==", + "dependencies": { + "@radix-ui/rect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-size": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.0.tgz", + "integrity": "sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-visually-hidden": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.1.0.tgz", @@ -3000,6 +3221,11 @@ } } }, + "node_modules/@radix-ui/rect": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.0.tgz", + "integrity": "sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==" + }, "node_modules/@react-aria/breadcrumbs": { "version": "3.5.13", "resolved": "https://registry.npmjs.org/@react-aria/breadcrumbs/-/breadcrumbs-3.5.13.tgz", diff --git a/package.json b/package.json index 7795424b1a..2c9b02c073 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "@nextui-org/theme": "2.2.5", "@radix-ui/react-dialog": "^1.1.1", "@radix-ui/react-icons": "^1.3.0", + "@radix-ui/react-select": "^2.1.1", "@radix-ui/react-toast": "^1.2.1", "@react-aria/ssr": "3.9.4", "@react-aria/visually-hidden": "3.8.12", From ae8098d53ea7a35ba41f2125715cf6ae1570c43d Mon Sep 17 00:00:00 2001 From: Sophia Dao Date: Wed, 7 Aug 2024 17:26:32 -0500 Subject: [PATCH 107/411] Findings card initial setup (#31) * feat(findings): WIP - add on click for row, select one row at a time, pass ID to function * feat(findings) More WIP for Findings Card - add in dummy data, pass selected row into card * feat(findings): Pass selected row through * Fix additional merge conflict * feat(findings): Update to new file structure * feat(findings): Hook up initial card with hard-coded data as a sidepanel that expands when a row element is clicked * Merge main --- app/(prowler)/findings/page.tsx | 64 ++++++++- components/findings/index.ts | 4 + .../findings/{ => table}/ColumnsFindings.tsx | 18 +-- .../findings/table/DataTableFindings.tsx | 127 +++++++++++++++++ components/findings/table/FindingsCard.tsx | 134 ++++++++++++++++++ .../{ => table}/SkeletonTableFindings.tsx | 0 components/index.ts | 22 ++- .../providers/table/DataTableProvider.tsx | 4 +- components/ui/sidebar/SidebarWrap.tsx | 2 +- types/components.ts | 20 ++- 10 files changed, 376 insertions(+), 19 deletions(-) create mode 100644 components/findings/index.ts rename components/findings/{ => table}/ColumnsFindings.tsx (78%) create mode 100644 components/findings/table/DataTableFindings.tsx create mode 100644 components/findings/table/FindingsCard.tsx rename components/findings/{ => table}/SkeletonTableFindings.tsx (100%) diff --git a/app/(prowler)/findings/page.tsx b/app/(prowler)/findings/page.tsx index 33a8746191..fd0ec5bedc 100644 --- a/app/(prowler)/findings/page.tsx +++ b/app/(prowler)/findings/page.tsx @@ -1,8 +1,11 @@ import { Spacer } from "@nextui-org/react"; import React, { Suspense } from "react"; -import { ColumnsFindings, SkeletonTableFindings } from "@/components"; -import { DataTableProvider } from "@/components/providers"; +import { + ColumnsFindings, + DataTableFindings, + SkeletonTableFindings, +} from "@/components/findings"; import { Header } from "@/components/ui"; export default async function Findings() { @@ -10,7 +13,7 @@ export default async function Findings() { <>
-
+
}> @@ -22,7 +25,7 @@ export default async function Findings() { const SSRDataTable = async () => { return ( - { service: "cloudformation", account: "dev (106908755756)", }, + card: { + resourceId: + "StackSet-AWSControlTowerBP-BASELINE-CLOUDWATCH-57c2a54c-9a36-4af9-a910-d1adb424c62a", + resourceLink: + "https://app.prowler.pro/app/findings?date=2024-08-05&search=StackSet-AWSControlTowerBP-BASELINE-CLOUDWATCH-57c2a54c-9a36-4af9-a910-d1adb424c62a", + resourceARN: + "arn:aws:cloudformation:eu-west-1:714274078102:stack/StackSet-AWSControlTowerBP-BASELINE-CLOUDWATCH-57c2a54c-9a36-4af9-a910-d1adb424c62a/9656eda0-909c-11ec-8fb2-06f4f86422d5", + checkId: "cloudformation_stack_outputs_find_secrets", + checkLink: + "https://app.prowler.pro/app/findings?date=2024-08-05&search=cloudformation_stack_outputs_find_secrets", + type: "Not applicable", + scanTime: "2024-08-05 @ 14:22:00 UTC", + findingId: "ba123291-03a5-49a1-b962-6fdb1d2b9c9b", + findingLink: + "https://app.prowler.pro/app/findings?date=2024-08-05&search=ba123291-03a5-49a1-b962-6fdb1d2b9c9b", + details: + "Potential secret found in Stack StackSet-AWSControlTowerBP-BASELINE-CLOUDWATCH-57c2a54c-9a36-4af9-a910-d1adb424c62a Outputs.", + riskLink: + "https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/outputs-section-structure.html", + riskDetails: + "Secrets hardcoded into CloudFormation outputs can be used by malware and bad actors to gain lateral access to other services.", + recommendationLink: + "https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-secretsmanager-secret-generatesecretstring.html", + recommendationDetails: + "Implement automated detective control to scan accounts for passwords and secrets. Use secrets manager service to store and retrieve passwords and secrets.", + referenceInformation: "CLI", + referenceLink: + "https://docs.prowler.com/checks/aws/secrets-policies/bc_aws_secrets_2/#cli-command", + }, }, { id: "67891", @@ -47,6 +79,30 @@ const SSRDataTable = async () => { service: "cloudformation", account: "stg (987654321987)", }, + card: { + resourceId: "", + resourceLink: + "https://app.prowler.pro/app/findings?search=%3Croot_account%3E", + resourceARN: "arn:aws:iam::714274078102:root", + checkId: "iam_root_mfa_enabled", + checkLink: + "https://app.prowler.pro/app/findings?search=iam_root_mfa_enabled", + type: "Software and Configuration Checks, Industry and Regulatory Standards, CIS AWS Foundations Benchmark", + scanTime: "2024-08-05 @ 14:22:00 UTC", + findingId: "bc3a34e0-16f0-4ea1-ac62-f796c8af3448", + findingLink: + "https://app.prowler.pro/app/findings?date=2024-08-05&search=bc3a34e0-16f0-4ea1-ac62-f796c8af3448", + details: "MFA is not enabled for root account.", + riskLink: "", + riskDetails: + "The root account is the most privileged user in an AWS account. MFA adds an extra layer of protection on top of a user name and password. With MFA enabled when a user signs in to an AWS website they will be prompted for their user name and password as well as for an authentication code from their AWS MFA device. When virtual MFA is used for root accounts it is recommended that the device used is NOT a personal device but rather a dedicated mobile device (tablet or phone) that is managed to be kept charged and secured independent of any individual personal devices. (non-personal virtual MFA) This lessens the risks of losing access to the MFA due to device loss / trade-in or if the individual owning the device is no longer employed at the company.", + recommendationLink: + "https://docs.aws.amazon.com/IAM/latest/UserGuide/id_root-user.html#id_root-user_manage_mfa", + recommendationDetails: + "Using IAM console navigate to Dashboard and expand Activate MFA on your root account.", + referenceInformation: "", + referenceLink: "", + }, }, ]} /> diff --git a/components/findings/index.ts b/components/findings/index.ts new file mode 100644 index 0000000000..20c1920f4e --- /dev/null +++ b/components/findings/index.ts @@ -0,0 +1,4 @@ +export * from "./table/ColumnsFindings"; +export * from "./table/DataTableFindings"; +export * from "./table/FindingsCard"; +export * from "./table/SkeletonTableFindings"; diff --git a/components/findings/ColumnsFindings.tsx b/components/findings/table/ColumnsFindings.tsx similarity index 78% rename from components/findings/ColumnsFindings.tsx rename to components/findings/table/ColumnsFindings.tsx index 90860b260e..afbd6c406f 100644 --- a/components/findings/ColumnsFindings.tsx +++ b/components/findings/table/ColumnsFindings.tsx @@ -10,19 +10,19 @@ import { import { ColumnDef } from "@tanstack/react-table"; import { VerticalDotsIcon } from "@/components/icons"; -import { FindingsProps } from "@/types"; +import { FindingProps } from "@/types"; -const getFindingsAttributes = (row: { original: FindingsProps }) => { +const getFindingsAttributes = (row: { original: FindingProps }) => { return row.original.attributes; }; -export const ColumnsFindings: ColumnDef[] = [ +export const ColumnsFindings: ColumnDef[] = [ { accessorKey: "checkTitle", header: "Check", cell: ({ row }) => { const { CheckTitle } = getFindingsAttributes(row); - return

{CheckTitle}

; + return

{CheckTitle}

; }, }, { @@ -30,7 +30,7 @@ export const ColumnsFindings: ColumnDef[] = [ header: "Severity", cell: ({ row }) => { const { severity } = getFindingsAttributes(row); - return

{severity}

; + return

{severity}

; }, }, { @@ -38,7 +38,7 @@ export const ColumnsFindings: ColumnDef[] = [ header: "Status", cell: ({ row }) => { const { status } = getFindingsAttributes(row); - return

{status}

; + return

{status}

; }, }, { @@ -46,7 +46,7 @@ export const ColumnsFindings: ColumnDef[] = [ header: "Region", cell: ({ row }) => { const { region } = getFindingsAttributes(row); - return

{region}

; + return

{region}

; }, }, { @@ -54,7 +54,7 @@ export const ColumnsFindings: ColumnDef[] = [ header: "Service", cell: ({ row }) => { const { service } = getFindingsAttributes(row); - return

{service}

; + return

{service}

; }, }, { @@ -62,7 +62,7 @@ export const ColumnsFindings: ColumnDef[] = [ header: "Account", cell: ({ row }) => { const { account } = getFindingsAttributes(row); - return

{account}

; + return

{account}

; }, }, { diff --git a/components/findings/table/DataTableFindings.tsx b/components/findings/table/DataTableFindings.tsx new file mode 100644 index 0000000000..5004dc2e6d --- /dev/null +++ b/components/findings/table/DataTableFindings.tsx @@ -0,0 +1,127 @@ +"use client"; + +import { Button } from "@nextui-org/react"; +import { + ColumnDef, + flexRender, + getCoreRowModel, + getPaginationRowModel, + Row, + useReactTable, +} from "@tanstack/react-table"; + +import { FindingsCard } from "@/components/findings/table/FindingsCard"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table/Table"; +import { FindingProps } from "@/types"; + +interface DataTableFindingsProps { + columns: ColumnDef[]; + data: TData[]; +} + +export function DataTableFindings({ + columns, + data, +}: DataTableFindingsProps) { + const table = useReactTable({ + data, + columns, + getCoreRowModel: getCoreRowModel(), + getPaginationRowModel: getPaginationRowModel(), + }); + + const selectedRow = table.getSelectedRowModel().rows[0]?.original; + + const onClick = (row: Row) => { + table.resetRowSelection(true); + row.toggleSelected(); + return; + }; + + return ( + <> +
+
+
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext(), + )} + + ); + })} + + ))} + + + {table.getRowModel().rows?.length ? ( + table.getRowModel().rows.map((row) => ( + onClick(row)} + className={"hover:cursor-pointer"} + > + {row.getVisibleCells().map((cell) => ( + + {flexRender( + cell.column.columnDef.cell, + cell.getContext(), + )} + + ))} + + )) + ) : ( + + + No results. + + + )} + +
+
+
+ + +
+
+ +
+ + ); +} diff --git a/components/findings/table/FindingsCard.tsx b/components/findings/table/FindingsCard.tsx new file mode 100644 index 0000000000..22056b29eb --- /dev/null +++ b/components/findings/table/FindingsCard.tsx @@ -0,0 +1,134 @@ +import { Button, Divider, Link } from "@nextui-org/react"; +import React from "react"; + +import { FindingProps } from "@/types"; +interface FindingsCardProps { + selectedRow: FindingProps; +} + +export const FindingsCard: React.FC = ({ selectedRow }) => { + const { attributes, card } = selectedRow || {}; + const { CheckTitle } = attributes || {}; + const { + resourceLink, + resourceId, + resourceARN, + checkLink, + checkId, + type, + scanTime, + findingLink, + findingId, + details, + riskDetails, + riskLink, + recommendationDetails, + recommendationLink, + referenceInformation, + referenceLink, + } = card || {}; + + return ( + <> +
+

{CheckTitle}

+ +
+

Resource ID:

+ + {resourceId} + +

Resource ARN:

+

{resourceARN}

+

Check ID:

+ + {checkId} + +

Type:

+

{type}

+

Scan Time:

+

{scanTime}

+

Prowler Finding ID:

+ + {findingId} + +
+ + {details && ( +
+

Details:

+

{details}

+
+ )} + + {riskDetails && ( +
+

+ Risk: + {riskLink && ( + + )} +

+

{riskDetails}

+
+ )} + + {recommendationDetails && ( +
+
+ Recommendation: + {recommendationLink && ( + + )} +
+

{recommendationDetails}

+
+ )} + + {referenceInformation && ( +
+
+ Reference Information: + {referenceLink && ( + + )} +
+

{referenceInformation}

+
+ )} +
+ + ); +}; diff --git a/components/findings/SkeletonTableFindings.tsx b/components/findings/table/SkeletonTableFindings.tsx similarity index 100% rename from components/findings/SkeletonTableFindings.tsx rename to components/findings/table/SkeletonTableFindings.tsx diff --git a/components/index.ts b/components/index.ts index df78769815..8fb02340c9 100644 --- a/components/index.ts +++ b/components/index.ts @@ -1,2 +1,20 @@ -export * from "./findings/ColumnsFindings"; -export * from "./findings/SkeletonTableFindings"; +export * from "./findings/table/ColumnsFindings"; +export * from "./findings/table/DataTableFindings"; +export * from "./findings/table/SkeletonTableFindings"; +export * from "./providers/AddProvider"; +export * from "./providers/ButtonAddProvider"; +export * from "./providers/ButtonCheckConnectionProvider"; +export * from "./providers/ButtonDeleteProvider"; +export * from "./providers/CheckConnectionProvider"; +export * from "./providers/DateWithTime"; +export * from "./providers/ProviderInfo"; +export * from "./providers/ScanStatus"; +export * from "./providers/table/ColumnsProvider"; +export * from "./providers/table/DataTableProvider"; +export * from "./providers/table/SkeletonTableProvider"; +export * from "./ui/alert/Alert"; +export * from "./ui/header/Header"; +export * from "./ui/sidebar"; +export * from "./ui/table/StatusBadge"; +export * from "./ui/table/Table"; +export * from "./ui/toast"; diff --git a/components/providers/table/DataTableProvider.tsx b/components/providers/table/DataTableProvider.tsx index a24202685e..9fa480bd16 100644 --- a/components/providers/table/DataTableProvider.tsx +++ b/components/providers/table/DataTableProvider.tsx @@ -18,7 +18,7 @@ import { TableRow, } from "@/components/ui/table/Table"; -interface DataTableProps { +interface DataTableProviderProps { columns: ColumnDef[]; data: TData[]; } @@ -26,7 +26,7 @@ interface DataTableProps { export function DataTableProvider({ columns, data, -}: DataTableProps) { +}: DataTableProviderProps) { const table = useReactTable({ data, columns, diff --git a/components/ui/sidebar/SidebarWrap.tsx b/components/ui/sidebar/SidebarWrap.tsx index 325e284f15..27d4a1af11 100644 --- a/components/ui/sidebar/SidebarWrap.tsx +++ b/components/ui/sidebar/SidebarWrap.tsx @@ -35,7 +35,7 @@ export const SidebarWrap = () => { return (
Date: Thu, 8 Aug 2024 10:51:52 -0500 Subject: [PATCH 108/411] chore(deps): bump django from 5.0.7 to 5.0.8 (#33) Bumps [django](https://github.com/django/django) from 5.0.7 to 5.0.8. - [Commits](https://github.com/django/django/compare/5.0.7...5.0.8) --- updated-dependencies: - dependency-name: django dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index e5e44befb9..8bca8ad202 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ -Django==5.0.7 +Django==5.0.8 djangorestframework==3.15.2 django-cors-headers==4.3.1 From bed2b1e7f789285fe2a05b5d68e3ee2b544151be Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Thu, 8 Aug 2024 20:08:11 +0200 Subject: [PATCH 109/411] feat: add Pagination - WIP --- actions/providers.ts | 29 ++++-- app/(prowler)/providers/page.tsx | 46 +++++++-- components/providers/AddProviderModal.tsx | 4 +- .../providers/table/DataTablePagination.tsx | 1 + .../providers/table/DataTableProvider.tsx | 1 - components/ui/index.ts | 1 + components/ui/pagination/Pagination.tsx | 95 +++++++++++++++++++ 7 files changed, 161 insertions(+), 16 deletions(-) create mode 100644 components/ui/pagination/Pagination.tsx diff --git a/actions/providers.ts b/actions/providers.ts index 30ac54ff5b..34471c0b1f 100644 --- a/actions/providers.ts +++ b/actions/providers.ts @@ -4,19 +4,36 @@ import { revalidatePath } from "next/cache"; import { parseStringify } from "@/lib"; -export const getProvider = async () => { +interface PaginationOptions { + page?: number; +} + +export const getProvider = async ({ page = 1 }: PaginationOptions) => { + if (isNaN(Number(page))) page = 1; + if (page < 1) page = 1; + const keyServer = process.env.LOCAL_SERVER_URL; try { - const providers = await fetch(`${keyServer}/providers`, { - headers: { - "X-Tenant-ID": `${process.env.HEADER_TENANT_ID}`, + const providers = await fetch( + `${keyServer}/providers?page%5Bnumber%5D=${page}`, + { + headers: { + "X-Tenant-ID": `${process.env.HEADER_TENANT_ID}`, + }, }, - }); + ); const data = await providers.json(); + const parsedData = parseStringify(data); revalidatePath("/providers"); - return parseStringify(data); + return { + providerDetails: parsedData?.data, + currentPage: parsedData?.meta?.pagination?.page, + totalPages: parsedData?.meta?.pagination?.pages, + totalItems: parsedData?.meta?.pagination?.count, + }; } catch (error) { + console.error("Error fetching providers:", error); return undefined; } }; diff --git a/app/(prowler)/providers/page.tsx b/app/(prowler)/providers/page.tsx index c58d57db4a..f429bb2acc 100644 --- a/app/(prowler)/providers/page.tsx +++ b/app/(prowler)/providers/page.tsx @@ -1,4 +1,5 @@ import { Spacer } from "@nextui-org/react"; +import { redirect } from "next/navigation"; import { Suspense } from "react"; import { getProvider } from "@/actions"; @@ -8,9 +9,16 @@ import { DataTableProvider, SkeletonTableProvider, } from "@/components/providers"; -import { Header } from "@/components/ui"; +import { Header, Pagination } from "@/components/ui"; -export default async function Providers() { +export default async function Providers({ + searchParams, +}: { + searchParams: { + page: number; + }; +}) { + console.log({ searchParams }, "los searchParamsss!"); return ( <>
@@ -20,18 +28,40 @@ export default async function Providers() {
- }> - + }> +
); } -const SSRDataTable = async () => { - const providersData = await getProvider(); - const [providers] = await Promise.all([providersData]); +const SSRDataTable = async ({ + searchParams, +}: { + searchParams: { + page: number; + }; +}) => { + // const perPage = searchParams['per_page'] ?? '5' + // const page = searchParams.page ? parseInt(searchParams.page) : 1; + const providersData = await getProvider({}); + // const [providers] = await Promise.all([providersData]); + // const { providerDetails, currentPage, totalPages, totalItems } = providersData; + // console.log(currentPage, totalPages, 'hehe') + // if (providers.meta.pagination.count === 0) { + // redirect('/'); + // } + // console.log(providers); + // const currentPage = providers.meta.pagination.page; + // const pages = providers.meta.pagination.pages; + // const count = providers.meta.pagination.count; + + // console.log(`Pages: ${pages}, Count: ${count}`); return ( - + <> + {/* */} + {/* */} + ); }; diff --git a/components/providers/AddProviderModal.tsx b/components/providers/AddProviderModal.tsx index a04c19c7bb..b9facbe627 100644 --- a/components/providers/AddProviderModal.tsx +++ b/components/providers/AddProviderModal.tsx @@ -48,7 +48,9 @@ export const AddProviderModal = () => { return ( - + diff --git a/components/providers/table/DataTablePagination.tsx b/components/providers/table/DataTablePagination.tsx index 6f31f826e6..4fe18c76dd 100644 --- a/components/providers/table/DataTablePagination.tsx +++ b/components/providers/table/DataTablePagination.tsx @@ -15,6 +15,7 @@ interface DataTablePaginationProps { export function DataTablePagination({ table, }: DataTablePaginationProps) { + // console.log(table); return (
diff --git a/components/providers/table/DataTableProvider.tsx b/components/providers/table/DataTableProvider.tsx index a6ddca5d65..522539d9e6 100644 --- a/components/providers/table/DataTableProvider.tsx +++ b/components/providers/table/DataTableProvider.tsx @@ -45,7 +45,6 @@ export function DataTableProvider({ sorting, }, }); - return ( <>
diff --git a/components/ui/index.ts b/components/ui/index.ts index f202f8ffe5..d2267337a5 100644 --- a/components/ui/index.ts +++ b/components/ui/index.ts @@ -1,6 +1,7 @@ export * from "./alert/Alert"; export * from "./dialog/Dialog"; export * from "./header/Header"; +export * from "./pagination/Pagination"; export * from "./select/Select"; export * from "./sidebar"; export * from "./table/StatusBadge"; diff --git a/components/ui/pagination/Pagination.tsx b/components/ui/pagination/Pagination.tsx new file mode 100644 index 0000000000..e490942398 --- /dev/null +++ b/components/ui/pagination/Pagination.tsx @@ -0,0 +1,95 @@ +"use client"; + +import Link from "next/link"; +import { usePathname, useSearchParams } from "next/navigation"; +import React from "react"; + +interface Props { + totalPages: number; + currentPage: number; + nextPage?: string; +} + +export const Pagination = ({ totalPages, currentPage }: Props) => { + const pathname = usePathname(); + const searchParams = useSearchParams(); + // const currentPage = searchParams["page"] ?? "1"; + const createPageUrl = (pageNumber: number | string) => { + const params = new URLSearchParams(searchParams); + + if (pageNumber === "...") return `${pathname}?${params.toString()}`; + + if (+pageNumber <= 0) { + return `${pathname}`; + } + if (+pageNumber > totalPages) { + return `${pathname}?${params.toString()}`; + } + params.set("page", pageNumber.toString()); + return `${pathname}?${params.toString()}`; + }; + + console.log(pathname, searchParams, currentPage); + + return ( +
+ +
+ ); +}; From b5b2e225ce7e6451982a6ba33839882dd693a4b7 Mon Sep 17 00:00:00 2001 From: Sophia Dao Date: Thu, 8 Aug 2024 15:04:47 -0500 Subject: [PATCH 110/411] Findings page Status component (#34) * feat(findings): Severity and Status badge WIP * Remove SeverityBadge from PR --- components/findings/table/ColumnsFindings.tsx | 5 ++-- components/index.ts | 20 --------------- components/ui/table/StatusBadge.tsx | 25 +++++++++++++------ types/components.ts | 4 +-- 4 files changed, 23 insertions(+), 31 deletions(-) delete mode 100644 components/index.ts diff --git a/components/findings/table/ColumnsFindings.tsx b/components/findings/table/ColumnsFindings.tsx index afbd6c406f..7c8b6f2396 100644 --- a/components/findings/table/ColumnsFindings.tsx +++ b/components/findings/table/ColumnsFindings.tsx @@ -10,6 +10,7 @@ import { import { ColumnDef } from "@tanstack/react-table"; import { VerticalDotsIcon } from "@/components/icons"; +import { StatusBadge } from "@/components/ui"; import { FindingProps } from "@/types"; const getFindingsAttributes = (row: { original: FindingProps }) => { @@ -30,7 +31,7 @@ export const ColumnsFindings: ColumnDef[] = [ header: "Severity", cell: ({ row }) => { const { severity } = getFindingsAttributes(row); - return

{severity}

; + return

{severity}

; }, }, { @@ -38,7 +39,7 @@ export const ColumnsFindings: ColumnDef[] = [ header: "Status", cell: ({ row }) => { const { status } = getFindingsAttributes(row); - return

{status}

; + return ; }, }, { diff --git a/components/index.ts b/components/index.ts deleted file mode 100644 index 8fb02340c9..0000000000 --- a/components/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -export * from "./findings/table/ColumnsFindings"; -export * from "./findings/table/DataTableFindings"; -export * from "./findings/table/SkeletonTableFindings"; -export * from "./providers/AddProvider"; -export * from "./providers/ButtonAddProvider"; -export * from "./providers/ButtonCheckConnectionProvider"; -export * from "./providers/ButtonDeleteProvider"; -export * from "./providers/CheckConnectionProvider"; -export * from "./providers/DateWithTime"; -export * from "./providers/ProviderInfo"; -export * from "./providers/ScanStatus"; -export * from "./providers/table/ColumnsProvider"; -export * from "./providers/table/DataTableProvider"; -export * from "./providers/table/SkeletonTableProvider"; -export * from "./ui/alert/Alert"; -export * from "./ui/header/Header"; -export * from "./ui/sidebar"; -export * from "./ui/table/StatusBadge"; -export * from "./ui/table/Table"; -export * from "./ui/toast"; diff --git a/components/ui/table/StatusBadge.tsx b/components/ui/table/StatusBadge.tsx index a1dea200aa..c11e7271cb 100644 --- a/components/ui/table/StatusBadge.tsx +++ b/components/ui/table/StatusBadge.tsx @@ -1,14 +1,25 @@ import { Chip } from "@nextui-org/react"; import React from "react"; -type Status = "completed" | "pending" | "cancelled"; +type Status = + | "completed" + | "pending" + | "cancelled" + | "fail" + | "success" + | "muted"; -export const statusColorMap: Record = - { - completed: "success", - pending: "warning", - cancelled: "danger", - }; +export const statusColorMap: Record< + Status, + "success" | "danger" | "warning" | "default" +> = { + completed: "success", + pending: "warning", + cancelled: "danger", + fail: "danger", + success: "success", + muted: "default", +}; export const StatusBadge = ({ status }: { status: Status }) => { return ( diff --git a/types/components.ts b/types/components.ts index 6741e118e2..209b6e4c0d 100644 --- a/types/components.ts +++ b/types/components.ts @@ -40,8 +40,8 @@ export interface FindingProps { id: string; attributes: { CheckTitle: string; - severity: string; - status: string; + severity: "critical" | "high" | "medium" | "low"; + status: "fail" | "success" | "muted"; region: string; service: string; account: string; From 5bb3c012c9deb5d7e69ce7ed6ce8d66dea61263e Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Fri, 9 Aug 2024 11:54:58 +0200 Subject: [PATCH 111/411] feat: add functionality to the Pagination component --- actions/providers.ts | 17 +--- app/(prowler)/providers/page.tsx | 48 +++------- .../providers/table/DataTablePagination.tsx | 86 ++++++++++------- .../providers/table/DataTableProvider.tsx | 8 +- components/ui/index.ts | 1 - components/ui/pagination/Pagination.tsx | 95 ------------------- lib/utils.ts | 10 ++ types/components.ts | 15 +++ 8 files changed, 100 insertions(+), 180 deletions(-) delete mode 100644 components/ui/pagination/Pagination.tsx diff --git a/actions/providers.ts b/actions/providers.ts index 34471c0b1f..6c6f3adee7 100644 --- a/actions/providers.ts +++ b/actions/providers.ts @@ -1,16 +1,12 @@ "use server"; import { revalidatePath } from "next/cache"; +import { redirect } from "next/navigation"; import { parseStringify } from "@/lib"; -interface PaginationOptions { - page?: number; -} - -export const getProvider = async ({ page = 1 }: PaginationOptions) => { - if (isNaN(Number(page))) page = 1; - if (page < 1) page = 1; +export const getProvider = async ({ page = 1 }) => { + if (isNaN(Number(page)) || page < 1) redirect("/providers"); const keyServer = process.env.LOCAL_SERVER_URL; @@ -26,12 +22,7 @@ export const getProvider = async ({ page = 1 }: PaginationOptions) => { const data = await providers.json(); const parsedData = parseStringify(data); revalidatePath("/providers"); - return { - providerDetails: parsedData?.data, - currentPage: parsedData?.meta?.pagination?.page, - totalPages: parsedData?.meta?.pagination?.pages, - totalItems: parsedData?.meta?.pagination?.count, - }; + return parsedData; } catch (error) { console.error("Error fetching providers:", error); return undefined; diff --git a/app/(prowler)/providers/page.tsx b/app/(prowler)/providers/page.tsx index f429bb2acc..2c79e94c79 100644 --- a/app/(prowler)/providers/page.tsx +++ b/app/(prowler)/providers/page.tsx @@ -9,16 +9,10 @@ import { DataTableProvider, SkeletonTableProvider, } from "@/components/providers"; -import { Header, Pagination } from "@/components/ui"; +import { Header } from "@/components/ui"; +import { searchParamsProps } from "@/types"; -export default async function Providers({ - searchParams, -}: { - searchParams: { - page: number; - }; -}) { - console.log({ searchParams }, "los searchParamsss!"); +export default async function Providers({ searchParams }: searchParamsProps) { return ( <>
@@ -36,32 +30,18 @@ export default async function Providers({ ); } -const SSRDataTable = async ({ - searchParams, -}: { - searchParams: { - page: number; - }; -}) => { - // const perPage = searchParams['per_page'] ?? '5' - // const page = searchParams.page ? parseInt(searchParams.page) : 1; - const providersData = await getProvider({}); - // const [providers] = await Promise.all([providersData]); - // const { providerDetails, currentPage, totalPages, totalItems } = providersData; - // console.log(currentPage, totalPages, 'hehe') - // if (providers.meta.pagination.count === 0) { - // redirect('/'); - // } - // console.log(providers); - // const currentPage = providers.meta.pagination.page; - // const pages = providers.meta.pagination.pages; - // const count = providers.meta.pagination.count; +const SSRDataTable = async ({ searchParams }: searchParamsProps) => { + const page = searchParams.page ? parseInt(searchParams.page) : 1; + const providersData = await getProvider({ page }); + const [providers] = await Promise.all([providersData]); + + if (providers?.errors) redirect("/providers"); - // console.log(`Pages: ${pages}, Count: ${count}`); return ( - <> - {/* */} - {/* */} - + ); }; diff --git a/components/providers/table/DataTablePagination.tsx b/components/providers/table/DataTablePagination.tsx index 4fe18c76dd..83fdd43be3 100644 --- a/components/providers/table/DataTablePagination.tsx +++ b/components/providers/table/DataTablePagination.tsx @@ -1,65 +1,83 @@ -import { Button } from "@nextui-org/react"; +"use client"; + import { ChevronLeftIcon, ChevronRightIcon, DoubleArrowLeftIcon, DoubleArrowRightIcon, } from "@radix-ui/react-icons"; -import { type Table } from "@tanstack/react-table"; +import Link from "next/link"; +import { redirect, usePathname, useSearchParams } from "next/navigation"; -interface DataTablePaginationProps { - table: Table; +import { extractPaginationInfo } from "@/lib"; +import { MetaDataProps } from "@/types"; + +interface DataTablePaginationProps { pageSizeOptions?: number[]; + metadata?: MetaDataProps; } -export function DataTablePagination({ - table, -}: DataTablePaginationProps) { - // console.log(table); +export function DataTablePagination({ metadata }: DataTablePaginationProps) { + if (!metadata) return null; + const pathname = usePathname(); + const searchParams = useSearchParams(); + + const { currentPage, totalPages, totalEntries } = + extractPaginationInfo(metadata); + + const createPageUrl = (pageNumber: number | string) => { + const params = new URLSearchParams(searchParams); + + if (pageNumber === "...") return `${pathname}?${params.toString()}`; + + if (+pageNumber > totalPages) { + return `${pathname}?${params.toString()}`; + } + + params.set("page", pageNumber.toString()); + return `${pathname}?${params.toString()}`; + }; + return (
+
+ {totalEntries} entries in Total. +
- Page {table.getState().pagination.pageIndex + 1} of{" "} - {table.getPageCount()} + Page {currentPage} of {totalPages}
- - - - +
diff --git a/components/providers/table/DataTableProvider.tsx b/components/providers/table/DataTableProvider.tsx index 522539d9e6..cfbb095e57 100644 --- a/components/providers/table/DataTableProvider.tsx +++ b/components/providers/table/DataTableProvider.tsx @@ -19,20 +19,22 @@ import { TableHeader, TableRow, } from "@/components/ui/table/Table"; +import { MetaDataProps } from "@/types"; import { DataTablePagination } from "./DataTablePagination"; interface DataTableProps { columns: ColumnDef[]; data: TData[]; + metadata?: MetaDataProps; } export function DataTableProvider({ columns, data, + metadata, }: DataTableProps) { const [sorting, setSorting] = useState([]); - const table = useReactTable({ data, columns, @@ -97,8 +99,8 @@ export function DataTableProvider({
-
- +
+
); diff --git a/components/ui/index.ts b/components/ui/index.ts index d2267337a5..f202f8ffe5 100644 --- a/components/ui/index.ts +++ b/components/ui/index.ts @@ -1,7 +1,6 @@ export * from "./alert/Alert"; export * from "./dialog/Dialog"; export * from "./header/Header"; -export * from "./pagination/Pagination"; export * from "./select/Select"; export * from "./sidebar"; export * from "./table/StatusBadge"; diff --git a/components/ui/pagination/Pagination.tsx b/components/ui/pagination/Pagination.tsx deleted file mode 100644 index e490942398..0000000000 --- a/components/ui/pagination/Pagination.tsx +++ /dev/null @@ -1,95 +0,0 @@ -"use client"; - -import Link from "next/link"; -import { usePathname, useSearchParams } from "next/navigation"; -import React from "react"; - -interface Props { - totalPages: number; - currentPage: number; - nextPage?: string; -} - -export const Pagination = ({ totalPages, currentPage }: Props) => { - const pathname = usePathname(); - const searchParams = useSearchParams(); - // const currentPage = searchParams["page"] ?? "1"; - const createPageUrl = (pageNumber: number | string) => { - const params = new URLSearchParams(searchParams); - - if (pageNumber === "...") return `${pathname}?${params.toString()}`; - - if (+pageNumber <= 0) { - return `${pathname}`; - } - if (+pageNumber > totalPages) { - return `${pathname}?${params.toString()}`; - } - params.set("page", pageNumber.toString()); - return `${pathname}?${params.toString()}`; - }; - - console.log(pathname, searchParams, currentPage); - - return ( -
- -
- ); -}; diff --git a/lib/utils.ts b/lib/utils.ts index c3e96dd8df..84a5fe24e3 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -1,6 +1,8 @@ import { type ClassValue, clsx } from "clsx"; import { twMerge } from "tailwind-merge"; +import { MetaDataProps } from "@/types"; + export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); } @@ -9,6 +11,14 @@ export const parseStringify = (value: any) => JSON.parse(JSON.stringify(value)); export const convertFileToUrl = (file: File) => URL.createObjectURL(file); +export const extractPaginationInfo = (metadata: MetaDataProps) => { + const currentPage = metadata?.pagination.page ?? "1"; + const totalPages = metadata?.pagination.pages; + const totalEntries = metadata?.pagination.count; + + return { currentPage, totalPages, totalEntries }; +}; + // FORMAT DATE TIME export const formatDateTime = ( dateString: Date | string, diff --git a/types/components.ts b/types/components.ts index b7b53724e5..1d23c712a9 100644 --- a/types/components.ts +++ b/types/components.ts @@ -9,6 +9,12 @@ export type IconProps = { style?: React.CSSProperties; }; +export interface searchParamsProps { + searchParams: { + page?: string; + }; +} + export interface ProviderProps { id: string; type: "providers"; @@ -47,3 +53,12 @@ export interface FindingsProps { account: string; }; } + +export interface MetaDataProps { + pagination: { + page: number; + pages: number; + count: number; + }; + version: string; +} From 10fc131e13f51c14bd351bdbae0ee652f3803dd2 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Fri, 9 Aug 2024 13:06:09 +0200 Subject: [PATCH 112/411] feat: remove dependency --- components/providers/table/DataTablePagination.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/providers/table/DataTablePagination.tsx b/components/providers/table/DataTablePagination.tsx index 83fdd43be3..0e195f0507 100644 --- a/components/providers/table/DataTablePagination.tsx +++ b/components/providers/table/DataTablePagination.tsx @@ -7,7 +7,7 @@ import { DoubleArrowRightIcon, } from "@radix-ui/react-icons"; import Link from "next/link"; -import { redirect, usePathname, useSearchParams } from "next/navigation"; +import { usePathname, useSearchParams } from "next/navigation"; import { extractPaginationInfo } from "@/lib"; import { MetaDataProps } from "@/types"; From 7ab46d61b5688752d41d5f5761c3944e0bba89ae Mon Sep 17 00:00:00 2001 From: Sophia Dao Date: Fri, 9 Aug 2024 09:16:55 -0500 Subject: [PATCH 113/411] Findings page Severity component (#35) * feat(findings): Severity and Status badge WIP * feat(findings): Status and Severity badge changes * Fix font color for dark mode --- app/(prowler)/findings/page.tsx | 314 +++++++++++++++++- components/findings/table/ColumnsFindings.tsx | 3 +- components/icons/Icons.tsx | 27 ++ components/ui/index.ts | 1 + components/ui/table/SeverityBadge.tsx | 49 +++ components/ui/table/StatusBadge.tsx | 32 +- 6 files changed, 412 insertions(+), 14 deletions(-) create mode 100644 components/ui/table/SeverityBadge.tsx diff --git a/app/(prowler)/findings/page.tsx b/app/(prowler)/findings/page.tsx index fd0ec5bedc..c551a3cb9f 100644 --- a/app/(prowler)/findings/page.tsx +++ b/app/(prowler)/findings/page.tsx @@ -33,7 +33,7 @@ const SSRDataTable = async () => { attributes: { CheckTitle: "Ensure users of groups with AdministratorAccess policy have MFA tokens enabled", - severity: "high", + severity: "critical", status: "fail", region: "us-west-2", service: "cloudformation", @@ -104,6 +104,318 @@ const SSRDataTable = async () => { referenceLink: "", }, }, + { + id: "67890", + attributes: { + CheckTitle: + "Ensure S3 buckets with public read/write access are not allowed", + severity: "high", + status: "fail", + region: "us-east-1", + service: "s3", + account: "prod (987654321012)", + }, + card: { + resourceId: "bucket-example-public-read-write", + resourceLink: + "https://app.prowler.pro/app/findings?date=2024-08-06&search=bucket-example-public-read-write", + resourceARN: "arn:aws:s3:::bucket-example-public-read-write", + checkId: "s3_bucket_public_access", + checkLink: + "https://app.prowler.pro/app/findings?date=2024-08-06&search=s3_bucket_public_access", + type: "Security", + scanTime: "2024-08-06 @ 10:45:00 UTC", + findingId: "e7b3d6a2-39a1-4f0e-9b78-29f4e6f292d1", + findingLink: + "https://app.prowler.pro/app/findings?date=2024-08-06&search=e7b3d6a2-39a1-4f0e-9b78-29f4e6f292d1", + details: + "S3 bucket example-public-read-write allows public read/write access.", + riskLink: + "https://docs.aws.amazon.com/AmazonS3/latest/dev/security-best-practices.html", + riskDetails: + "Publicly accessible S3 buckets can expose sensitive data and be exploited by attackers.", + recommendationLink: + "https://docs.aws.amazon.com/AmazonS3/latest/user-guide/block-public-access.html", + recommendationDetails: + "Use S3 Block Public Access to prevent public access to your S3 buckets.", + referenceInformation: "AWS Console", + referenceLink: + "https://docs.prowler.com/checks/aws/s3-policies/bc_aws_s3_1/#console", + }, + }, + { + id: "11223", + attributes: { + CheckTitle: + "Ensure IAM password policy requires minimum length of 12 characters", + severity: "medium", + status: "fail", + region: "eu-central-1", + service: "iam", + account: "staging (123456789012)", + }, + card: { + resourceId: "password-policy", + resourceLink: + "https://app.prowler.pro/app/findings?date=2024-08-06&search=password-policy", + resourceARN: "arn:aws:iam::123456789012:password-policy", + checkId: "iam_password_policy_min_length", + checkLink: + "https://app.prowler.pro/app/findings?date=2024-08-06&search=iam_password_policy_min_length", + type: "Security", + scanTime: "2024-08-06 @ 11:10:00 UTC", + findingId: "c2b3d1a4-7a9f-4d5c-a9ef-765a1d7f421c", + findingLink: + "https://app.prowler.pro/app/findings?date=2024-08-06&search=c2b3d1a4-7a9f-4d5c-a9ef-765a1d7f421c", + details: + "IAM password policy does not require a minimum length of 12 characters.", + riskLink: + "https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html", + riskDetails: + "Weak password policies increase the risk of unauthorized access to AWS resources.", + recommendationLink: + "https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_passwords_account-policy.html", + recommendationDetails: + "Enforce a minimum password length of 12 characters in your IAM password policy.", + referenceInformation: "AWS CLI", + referenceLink: + "https://docs.prowler.com/checks/aws/iam-policies/bc_aws_iam_1/#cli-command", + }, + }, + { + id: "44556", + attributes: { + CheckTitle: "Ensure RDS instances are not publicly accessible", + severity: "high", + status: "muted", + region: "ap-southeast-1", + service: "rds", + account: "prod (234567890123)", + }, + card: { + resourceId: "rds-instance-public-access", + resourceLink: + "https://app.prowler.pro/app/findings?date=2024-08-06&search=rds-instance-public-access", + resourceARN: + "arn:aws:rds:ap-southeast-1:234567890123:db:rds-instance-public-access", + checkId: "rds_instance_public_access", + checkLink: + "https://app.prowler.pro/app/findings?date=2024-08-06&search=rds_instance_public_access", + type: "Security", + scanTime: "2024-08-06 @ 13:15:00 UTC", + findingId: "f3b4c5d6-19a7-45d8-bc3e-8c5f6a7d8e9b", + findingLink: + "https://app.prowler.pro/app/findings?date=2024-08-06&search=f3b4c5d6-19a7-45d8-bc3e-8c5f6a7d8e9b", + details: + "RDS instance is not publicly accessible, which adheres to security best practices.", + riskLink: + "https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Overview.RDSSecurity.html", + riskDetails: + "Publicly accessible RDS instances can be attacked by unauthorized users.", + recommendationLink: + "https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_VPC.Scenarios.html", + recommendationDetails: + "Ensure RDS instances are not publicly accessible by placing them in private subnets.", + referenceInformation: "AWS Console", + referenceLink: + "https://docs.prowler.com/checks/aws/rds-policies/bc_aws_rds_1/#console", + }, + }, + { + id: "77889", + attributes: { + CheckTitle: "Ensure EBS volumes are encrypted", + severity: "critical", + status: "fail", + region: "eu-west-1", + service: "ec2", + account: "prod (345678901234)", + }, + card: { + resourceId: "volume-0123456789abcdef0", + resourceLink: + "https://app.prowler.pro/app/findings?date=2024-08-07&search=volume-0123456789abcdef0", + resourceARN: + "arn:aws:ec2:eu-west-1:345678901234:volume/volume-0123456789abcdef0", + checkId: "ebs_volume_encryption", + checkLink: + "https://app.prowler.pro/app/findings?date=2024-08-07&search=ebs_volume_encryption", + type: "Encryption", + scanTime: "2024-08-07 @ 09:30:00 UTC", + findingId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + findingLink: + "https://app.prowler.pro/app/findings?date=2024-08-07&search=a1b2c3d4-e5f6-7890-abcd-ef1234567890", + details: "EBS volume volume-0123456789abcdef0 is not encrypted.", + riskLink: + "https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EBSEncryption.html", + riskDetails: + "Unencrypted EBS volumes can expose sensitive data if compromised.", + recommendationLink: + "https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EBSEncryption.html", + recommendationDetails: + "Enable encryption for EBS volumes using AWS-managed or customer-managed keys.", + referenceInformation: "AWS CLI", + referenceLink: + "https://docs.prowler.com/checks/aws/ec2-policies/bc_aws_ec2_1/#cli-command", + }, + }, + { + id: "99100", + attributes: { + CheckTitle: "Ensure CloudTrail is enabled in all regions", + severity: "critical", + status: "fail", + region: "us-west-2", + service: "cloudtrail", + account: "dev (456789012345)", + }, + card: { + resourceId: "cloudtrail-all-regions", + resourceLink: + "https://app.prowler.pro/app/findings?date=2024-08-07&search=cloudtrail-all-regions", + resourceARN: + "arn:aws:cloudtrail:us-west-2:456789012345:trail/cloudtrail-all-regions", + checkId: "cloudtrail_all_regions_enabled", + checkLink: + "https://app.prowler.pro/app/findings?date=2024-08-07&search=cloudtrail_all_regions_enabled", + type: "Logging", + scanTime: "2024-08-07 @ 11:00:00 UTC", + findingId: "b2c3d4e5-f6a7-8901-bcde-f123456789ab", + findingLink: + "https://app.prowler.pro/app/findings?date=2024-08-07&search=b2c3d4e5-f6a7-8901-bcde-f123456789ab", + details: + "CloudTrail is not enabled for all regions in the account.", + riskLink: + "https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-best-practices.html", + riskDetails: + "Without CloudTrail enabled in all regions, activities in non-monitored regions may go unnoticed.", + recommendationLink: + "https://docs.aws.amazon.com/awscloudtrail/latest/userguide/creating-trail-using-console.html", + recommendationDetails: + "Enable CloudTrail across all regions to ensure comprehensive monitoring.", + referenceInformation: "AWS Console", + referenceLink: + "https://docs.prowler.com/checks/aws/cloudtrail-policies/bc_aws_cloudtrail_1/#console", + }, + }, + { + id: "22334", + attributes: { + CheckTitle: "Ensure EC2 instances do not use outdated AMIs", + severity: "medium", + status: "fail", + region: "us-east-1", + service: "ec2", + account: "prod (567890123456)", + }, + card: { + resourceId: "instance-ami-outdated", + resourceLink: + "https://app.prowler.pro/app/findings?date=2024-08-07&search=instance-ami-outdated", + resourceARN: + "arn:aws:ec2:us-east-1:567890123456:instance/instance-ami-outdated", + checkId: "ec2_instance_outdated_ami", + checkLink: + "https://app.prowler.pro/app/findings?date=2024-08-07&search=ec2_instance_outdated_ami", + type: "Configuration", + scanTime: "2024-08-07 @ 12:45:00 UTC", + findingId: "c3d4e5f6-a789-0123-bcde-f234567890ab", + findingLink: + "https://app.prowler.pro/app/findings?date=2024-08-07&search=c3d4e5f6-a789-0123-bcde-f234567890ab", + details: + "EC2 instance instance-ami-outdated is using an outdated AMI.", + riskLink: + "https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/AMIs.html", + riskDetails: + "Outdated AMIs may have unpatched vulnerabilities that can be exploited.", + recommendationLink: + "https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/creating-an-ami.html", + recommendationDetails: + "Update EC2 instances to use the latest AMIs with all security patches applied.", + referenceInformation: "AWS CLI", + referenceLink: + "https://docs.prowler.com/checks/aws/ec2-policies/bc_aws_ec2_2/#cli-command", + }, + }, + { + id: "55667", + attributes: { + CheckTitle: + "Ensure CloudWatch alarms exist for critical system events", + severity: "high", + status: "muted", + region: "eu-west-2", + service: "cloudwatch", + account: "prod (678901234567)", + }, + card: { + resourceId: "cloudwatch-alarms", + resourceLink: + "https://app.prowler.pro/app/findings?date=2024-08-08&search=cloudwatch-alarms", + resourceARN: + "arn:aws:cloudwatch:eu-west-2:678901234567:alarm/cloudwatch-alarms", + checkId: "cloudwatch_alarms_for_critical_events", + checkLink: + "https://app.prowler.pro/app/findings?date=2024-08-08&search=cloudwatch_alarms_for_critical_events", + type: "Monitoring", + scanTime: "2024-08-08 @ 09:50:00 UTC", + findingId: "d4e5f6a7-8901-bcde-f345678901ab", + findingLink: + "https://app.prowler.pro/app/findings?date=2024-08-08&search=d4e5f6a7-8901-bcde-f345678901ab", + details: + "CloudWatch alarms are correctly configured for critical system events.", + riskLink: + "https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatchAlarms.html", + riskDetails: + "Without alarms for critical events, key issues may go unnoticed, leading to outages.", + recommendationLink: + "https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Create_Alarm.html", + recommendationDetails: + "Ensure alarms are set up for critical events to trigger timely notifications.", + referenceInformation: "AWS Console", + referenceLink: + "https://docs.prowler.com/checks/aws/cloudwatch-policies/bc_aws_cloudwatch_1/#console", + }, + }, + { + id: "88900", + attributes: { + CheckTitle: "Ensure IAM users have least privilege permissions", + severity: "medium", + status: "fail", + region: "ap-northeast-1", + service: "iam", + account: "dev (789012345678)", + }, + card: { + resourceId: "iam-user-least-privilege", + resourceLink: + "https://app.prowler.pro/app/findings?date=2024-08-08&search=iam-user-least-privilege", + resourceARN: + "arn:aws:iam::789012345678:user/iam-user-least-privilege", + checkId: "iam_user_least_privilege", + checkLink: + "https://app.prowler.pro/app/findings?date=2024-08-08&search=iam_user_least_privilege", + type: "Security", + scanTime: "2024-08-08 @ 10:15:00 UTC", + findingId: "e5f6a7b8-9012-bcde-f456789012ab", + findingLink: + "https://app.prowler.pro/app/findings?date=2024-08-08&search=e5f6a7b8-9012-bcde-f456789012ab", + details: + "IAM user iam-user-least-privilege has more permissions than necessary.", + riskLink: + "https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html", + riskDetails: + "Excessive permissions increase the risk of privilege escalation and security breaches.", + recommendationLink: + "https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies.html", + recommendationDetails: + "Apply the principle of least privilege to IAM users by restricting their permissions to the minimum necessary.", + referenceInformation: "AWS CLI", + referenceLink: + "https://docs.prowler.com/checks/aws/iam-policies/bc_aws_iam_2/#cli-command", + }, + }, ]} /> ); diff --git a/components/findings/table/ColumnsFindings.tsx b/components/findings/table/ColumnsFindings.tsx index 7c8b6f2396..11d6d7b02c 100644 --- a/components/findings/table/ColumnsFindings.tsx +++ b/components/findings/table/ColumnsFindings.tsx @@ -11,6 +11,7 @@ import { ColumnDef } from "@tanstack/react-table"; import { VerticalDotsIcon } from "@/components/icons"; import { StatusBadge } from "@/components/ui"; +import { SeverityBadge } from "@/components/ui"; import { FindingProps } from "@/types"; const getFindingsAttributes = (row: { original: FindingProps }) => { @@ -31,7 +32,7 @@ export const ColumnsFindings: ColumnDef[] = [ header: "Severity", cell: ({ row }) => { const { severity } = getFindingsAttributes(row); - return

{severity}

; + return ; }, }, { diff --git a/components/icons/Icons.tsx b/components/icons/Icons.tsx index 485baf39d4..a929e7f750 100644 --- a/components/icons/Icons.tsx +++ b/components/icons/Icons.tsx @@ -413,3 +413,30 @@ export const RocketIcon: React.FC = ({ ); }; + +export const AlertIcon: React.FC = ({ + size = 24, + width, + height, + ...props +}) => ( + +); diff --git a/components/ui/index.ts b/components/ui/index.ts index f202f8ffe5..deeae1b0d6 100644 --- a/components/ui/index.ts +++ b/components/ui/index.ts @@ -3,6 +3,7 @@ export * from "./dialog/Dialog"; export * from "./header/Header"; export * from "./select/Select"; export * from "./sidebar"; +export * from "./table/SeverityBadge"; export * from "./table/StatusBadge"; export * from "./table/Table"; export * from "./toast"; diff --git a/components/ui/table/SeverityBadge.tsx b/components/ui/table/SeverityBadge.tsx new file mode 100644 index 0000000000..7d9760c22e --- /dev/null +++ b/components/ui/table/SeverityBadge.tsx @@ -0,0 +1,49 @@ +import { Chip } from "@nextui-org/react"; +import React from "react"; + +import { AlertIcon } from "@/components/icons"; + +type Severity = "critical" | "high" | "medium" | "low"; + +const severityColorMap: Record< + Severity, + | "text-white bg-red-800" + | "text-white bg-red-600" + | "text-white bg-orange-500" + | "bg-yellow-200" +> = { + critical: "text-white bg-red-800", + high: "text-white bg-red-600", + medium: "text-white bg-orange-500", + low: "bg-yellow-200", +}; + +const severityIconMap: Partial> = { + critical: , +}; + +const getSeverityColor: (severity: Severity) => string = (severity) => + // eslint-disable-next-line security/detect-object-injection + severityColorMap[severity]; + +const getSeverityIcon: (severity: Severity) => React.ReactNode | null = ( + severity, +) => + // eslint-disable-next-line security/detect-object-injection + severityIconMap[severity] || null; + +export const SeverityBadge = ({ severity }: { severity: Severity }) => { + return ( + + {severity} + + ); +}; diff --git a/components/ui/table/StatusBadge.tsx b/components/ui/table/StatusBadge.tsx index c11e7271cb..d696e89ccf 100644 --- a/components/ui/table/StatusBadge.tsx +++ b/components/ui/table/StatusBadge.tsx @@ -9,26 +9,34 @@ type Status = | "success" | "muted"; -export const statusColorMap: Record< +const statusColorMap: Record< Status, - "success" | "danger" | "warning" | "default" + | "text-white bg-green-600" + | "bg-yellow-200" + | "text-white bg-red-600" + | "bg-gray-300" > = { - completed: "success", - pending: "warning", - cancelled: "danger", - fail: "danger", - success: "success", - muted: "default", + completed: "text-white bg-green-600", + pending: "bg-yellow-200", + cancelled: "text-white bg-red-600", + fail: "text-white bg-red-600", + success: "text-white bg-green-600", + muted: "bg-gray-300", }; +const getStatusColor: (status: Status) => string = (status) => + // eslint-disable-next-line security/detect-object-injection + statusColorMap[status]; + export const StatusBadge = ({ status }: { status: Status }) => { return ( {status} From c492d25f4c3a200b31d12d54a6b303f1907c97b8 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Mon, 12 Aug 2024 18:41:17 +0200 Subject: [PATCH 114/411] Prwlr 4408 clean area labels warning in the console tab (#36) * feat: remove 2 high severity vulnerabilities * chore: solve accesibility warnings * feat: all accesibility warnings have been solved --- .eslintrc.cjs | 7 +- components/providers/AddProvider.tsx | 3 + .../providers/table/ColumnsProvider.tsx | 4 +- components/ui/sidebar/Sidebar.tsx | 10 +- components/ui/sidebar/SidebarWrap.tsx | 14 +- package-lock.json | 509 +----------------- package.json | 4 +- 7 files changed, 41 insertions(+), 510 deletions(-) diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 902ee59d19..230bd6691f 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -4,12 +4,12 @@ module.exports = { es2021: true, }, parser: "@typescript-eslint/parser", - plugins: ["prettier", "@typescript-eslint", "simple-import-sort"], + plugins: ["prettier", "@typescript-eslint", "simple-import-sort", "jsx-a11y"], extends: [ "eslint:recommended", - "plugin:@typescript-eslint/eslint-recommended", "plugin:@typescript-eslint/recommended", "plugin:security/recommended-legacy", + "plugin:jsx-a11y/recommended", "prettier", ], parserOptions: { @@ -22,7 +22,6 @@ module.exports = { rules: { "no-console": 1, eqeqeq: 2, - // indent: ["error", 2, { SwitchCase: 1 }], // disabled because it clashes with prettier's indent quotes: ["error", "double", "avoid-escape"], "@typescript-eslint/no-explicit-any": "off", "prettier/prettier": [ @@ -36,5 +35,7 @@ module.exports = { "eol-last": ["error", "always"], "simple-import-sort/imports": "error", "simple-import-sort/exports": "error", + "jsx-a11y/anchor-is-valid": "error", + "jsx-a11y/alt-text": "error", }, }; diff --git a/components/providers/AddProvider.tsx b/components/providers/AddProvider.tsx index 5417f0ffe4..b8880da1e9 100644 --- a/components/providers/AddProvider.tsx +++ b/components/providers/AddProvider.tsx @@ -40,18 +40,21 @@ export const AddProvider = () => { type="text" name="provider" placeholder="Provider" + aria-label="Provider" className="py-2 px-3 rounded-sm" /> diff --git a/components/providers/table/ColumnsProvider.tsx b/components/providers/table/ColumnsProvider.tsx index bd23e3d9c7..663c076477 100644 --- a/components/providers/table/ColumnsProvider.tsx +++ b/components/providers/table/ColumnsProvider.tsx @@ -109,10 +109,10 @@ export const ColumnsProvider: ColumnDef[] = [ - + - + diff --git a/components/ui/sidebar/Sidebar.tsx b/components/ui/sidebar/Sidebar.tsx index 3a677ea20e..7ab1cfe163 100644 --- a/components/ui/sidebar/Sidebar.tsx +++ b/components/ui/sidebar/Sidebar.tsx @@ -15,7 +15,7 @@ import { Tooltip, } from "@nextui-org/react"; import clsx from "clsx"; -import React from "react"; +import React, { useState } from "react"; export enum SidebarItemType { Nest = "nest", @@ -61,8 +61,7 @@ const Sidebar = React.forwardRef( }, ref, ) => { - const [selected, setSelected] = - React.useState(defaultSelectedKey); + const [selected, setSelected] = useState(defaultSelectedKey); const sectionClasses = { ...sectionClassesProp, @@ -100,6 +99,7 @@ const Sidebar = React.forwardRef( ( iconClassName, )} icon={item.icon} + aria-hidden={"true"} width={24} /> ) : ( @@ -140,6 +141,7 @@ const Sidebar = React.forwardRef( "text-default-500 group-data-[selected=true]:text-foreground", iconClassName, )} + aria-hidden={"true"} icon={item.icon} width={24} /> @@ -169,6 +171,7 @@ const Sidebar = React.forwardRef( "text-default-500 group-data-[selected=true]:text-foreground", iconClassName, )} + aria-hidden="true" icon={item.icon} width={24} /> @@ -275,6 +278,7 @@ const Sidebar = React.forwardRef( list: clsx("items-center", classNames?.list), }} color="default" + aria-label="Navigation Menu" itemClasses={{ ...itemClasses, base: clsx( diff --git a/components/ui/sidebar/SidebarWrap.tsx b/components/ui/sidebar/SidebarWrap.tsx index 27d4a1af11..2c009b1820 100644 --- a/components/ui/sidebar/SidebarWrap.tsx +++ b/components/ui/sidebar/SidebarWrap.tsx @@ -90,6 +90,7 @@ export const SidebarWrap = () => { placement="right" >
{ }, )} > - +
{ placement={isCompact ? "right" : "top"} > diff --git a/package-lock.json b/package-lock.json index 3ef31ce458..2729d64a84 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,7 +31,7 @@ "react": "18.3.1", "react-dom": "18.3.1", "server-only": "^0.0.1", - "shadcn-ui": "^0.8.0", + "shadcn-ui": "^0.2.3", "tailwind-merge": "^2.4.0", "tailwindcss-animate": "^1.0.7" }, @@ -47,7 +47,7 @@ "eslint-config-next": "14.2.1", "eslint-config-prettier": "^9.1.0", "eslint-plugin-import": "^2.26.0", - "eslint-plugin-jsx-a11y": "^6.4.1", + "eslint-plugin-jsx-a11y": "^6.9.0", "eslint-plugin-node": "^11.1.0", "eslint-plugin-prettier": "^5.1.3", "eslint-plugin-react": "^7.23.2", @@ -75,18 +75,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@antfu/ni": { "version": "0.21.12", "resolved": "https://registry.npmjs.org/@antfu/ni/-/ni-0.21.12.tgz", @@ -113,254 +101,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/compat-data": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.2.tgz", - "integrity": "sha512-bYcppcpKBvX4znYaPEeFau03bp89ShqNMLs+rmdptMw+heSZh9+z84d2YG+K7cYLbWwzdjtDoW/uqZmPjulClQ==", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz", - "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==", - "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.25.0", - "@babel/helper-compilation-targets": "^7.25.2", - "@babel/helper-module-transforms": "^7.25.2", - "@babel/helpers": "^7.25.0", - "@babel/parser": "^7.25.0", - "@babel/template": "^7.25.0", - "@babel/traverse": "^7.25.2", - "@babel/types": "^7.25.2", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/core/node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/generator": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.0.tgz", - "integrity": "sha512-3LEEcj3PVW8pW2R1SR1M89g/qrYk/m/mB/tLqn7dn4sbBUQyTqnlod+II2U4dqiGtUmkcnAmkMDralTFZttRiw==", - "dependencies": { - "@babel/types": "^7.25.0", - "@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-annotate-as-pure": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz", - "integrity": "sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg==", - "dependencies": { - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz", - "integrity": "sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==", - "dependencies": { - "@babel/compat-data": "^7.25.2", - "@babel/helper-validator-option": "^7.24.8", - "browserslist": "^4.23.1", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.0.tgz", - "integrity": "sha512-GYM6BxeQsETc9mnct+nIIpf63SAyzvyYN7UB/IlTyd+MBg06afFGp0mIeUqGyWgS2mxad6vqbMrHVlaL3m70sQ==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.24.7", - "@babel/helper-member-expression-to-functions": "^7.24.8", - "@babel/helper-optimise-call-expression": "^7.24.7", - "@babel/helper-replace-supers": "^7.25.0", - "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", - "@babel/traverse": "^7.25.0", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.8.tgz", - "integrity": "sha512-LABppdt+Lp/RlBxqrh4qgf1oEH/WxdzQNDJIu5gC/W1GyvPVrOBiItmmM8wan2fm4oYqFuFfkXmlGpLQhPY8CA==", - "dependencies": { - "@babel/traverse": "^7.24.8", - "@babel/types": "^7.24.8" - }, - "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==", - "dependencies": { - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz", - "integrity": "sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==", - "dependencies": { - "@babel/helper-module-imports": "^7.24.7", - "@babel/helper-simple-access": "^7.24.7", - "@babel/helper-validator-identifier": "^7.24.7", - "@babel/traverse": "^7.25.2" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.24.7.tgz", - "integrity": "sha512-jKiTsW2xmWwxT1ixIdfXUZp+P5yURx2suzLZr5Hi64rURpDYdMW0pv+Uf17EYk2Rd428Lx4tLsnjGJzYKDM/6A==", - "dependencies": { - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz", - "integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.25.0.tgz", - "integrity": "sha512-q688zIvQVYtZu+i2PsdIu/uWGRpfxzr5WESsfpShfZECkO+d2o+WROWezCi/Q6kJ0tfPa5+pUGUlfx2HhrA3Bg==", - "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.24.8", - "@babel/helper-optimise-call-expression": "^7.24.7", - "@babel/traverse": "^7.25.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-simple-access": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", - "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", - "dependencies": { - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.24.7.tgz", - "integrity": "sha512-IO+DLT3LQUElMbpzlatRASEyQtfhSE0+m465v++3jyyXeBTBUjtVZg28/gHeV5mrTJqvEKhKroBGAvhW+qPHiQ==", - "dependencies": { - "@babel/traverse": "^7.24.7", - "@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==", - "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", @@ -369,26 +109,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-validator-option": { - "version": "7.24.8", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz", - "integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.0.tgz", - "integrity": "sha512-MjgLZ42aCm0oGjJj8CtSM3DB8NOOf8h2l7DCTePJs29u+v7yO/RBX9nShlKMgFnRks/Q4tBAe7Hxnov9VkGwLw==", - "dependencies": { - "@babel/template": "^7.25.0", - "@babel/types": "^7.25.0" - }, - "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", @@ -467,49 +187,6 @@ "node": ">=4" } }, - "node_modules/@babel/parser": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.0.tgz", - "integrity": "sha512-CzdIU9jdP0dg7HdyB+bHvDJGagUv+qtzZt5rYCWwW6tITNqV9odjp6Qu41gkG0ca5UfdDUWrKkiAnHHdGRnOrA==", - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.7.tgz", - "integrity": "sha512-c/+fVeJBB0FeKsFvwytYiUD+LBvhHjGSI0g446PRGdSVGZLRNArBUno2PETbAly3tpiNAQR5XaZ+JslxkotsbA==", - "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-typescript": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.25.2.tgz", - "integrity": "sha512-lBwRvjSmqiMYe/pS0+1gggjJleUJi7NzjvQ1Fkqtt69hBa/0t1YuW/MLQMAPixfwaQOHUXsd6jeU3Z+vdGv3+A==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.24.7", - "@babel/helper-create-class-features-plugin": "^7.25.0", - "@babel/helper-plugin-utils": "^7.24.8", - "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", - "@babel/plugin-syntax-typescript": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/runtime": { "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.7.tgz", @@ -521,57 +198,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/template": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz", - "integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==", - "dependencies": { - "@babel/code-frame": "^7.24.7", - "@babel/parser": "^7.25.0", - "@babel/types": "^7.25.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.2.tgz", - "integrity": "sha512-s4/r+a7xTnny2O6FcZzqgT6nE4/GHEdcqj4qAeglbUOh0TeglEfmNJFAd/OLoVtGd6ZhAO8GCVvCNUO5t/VJVQ==", - "dependencies": { - "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.25.0", - "@babel/parser": "^7.25.0", - "@babel/template": "^7.25.0", - "@babel/types": "^7.25.2", - "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==", - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/types": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.2.tgz", - "integrity": "sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==", - "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/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -5010,17 +4636,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/ast-types": { - "version": "0.16.1", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.16.1.tgz", - "integrity": "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==", - "dependencies": { - "tslib": "^2.0.1" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/ast-types-flow": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", @@ -5165,6 +4780,7 @@ "version": "4.23.1", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.1.tgz", "integrity": "sha512-TUfofFo/KsK/bWZ9TWQ5O26tsWW4Uhmt8IYklbnUa70udB6P2wA7w7o4PY4muaEPBQaAX+CEnmmIA41NVHtPVw==", + "dev": true, "funding": [ { "type": "opencollective", @@ -5533,11 +5149,6 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" - }, "node_modules/cosmiconfig": { "version": "8.3.6", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", @@ -5829,7 +5440,8 @@ "node_modules/electron-to-chromium": { "version": "1.4.818", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.818.tgz", - "integrity": "sha512-eGvIk2V0dGImV9gWLq8fDfTTsCAeMDwZqEPMr+jMInxZdnp9Us8UpovYpRCf9NQ7VOFgrN2doNSgvISbsbNpxA==" + "integrity": "sha512-eGvIk2V0dGImV9gWLq8fDfTTsCAeMDwZqEPMr+jMInxZdnp9Us8UpovYpRCf9NQ7VOFgrN2doNSgvISbsbNpxA==", + "dev": true }, "node_modules/emoji-regex": { "version": "9.2.2", @@ -6044,6 +5656,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "dev": true, "engines": { "node": ">=6" } @@ -6835,18 +6448,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/esquery": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", @@ -7191,14 +6792,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/get-east-asian-width": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.2.0.tgz", @@ -8085,17 +7678,6 @@ "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==", - "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", @@ -8389,11 +7971,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lodash._reinterpolate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", - "integrity": "sha512-xYHt68QRoYGjeeM/XOE1uJtvXQAgvszfBhjV4yvsQH0u2i9I6cI6c6/eG4Hh3UAOVn0y/xAXwmTzEay49Q//HA==" - }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -8430,23 +8007,6 @@ "resolved": "https://registry.npmjs.org/lodash.omit/-/lodash.omit-4.5.0.tgz", "integrity": "sha512-XeqSp49hNGmlkj2EJlfrQFIzQ6lXdNro9sddtQzcJY8QaoC2GO0DT7xaIokHeyM+mIT0mPMlPvkYzg2xCuHdZg==" }, - "node_modules/lodash.template": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz", - "integrity": "sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==", - "dependencies": { - "lodash._reinterpolate": "^3.0.0", - "lodash.templatesettings": "^4.0.0" - } - }, - "node_modules/lodash.templatesettings": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz", - "integrity": "sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==", - "dependencies": { - "lodash._reinterpolate": "^3.0.0" - } - }, "node_modules/log-symbols": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-5.1.0.tgz", @@ -8880,7 +8440,8 @@ "node_modules/node-releases": { "version": "2.0.14", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", - "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==" + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", + "dev": true }, "node_modules/normalize-path": { "version": "3.0.0", @@ -9716,21 +9277,6 @@ "node": ">=8.10.0" } }, - "node_modules/recast": { - "version": "0.23.9", - "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.9.tgz", - "integrity": "sha512-Hx/BGIbwj+Des3+xy5uAtAbdCyqK9y9wbBcDFDYanLS9JnMqf7OeF87HQwUimE87OEc72mr6tkKUKMBBL+hF9Q==", - "dependencies": { - "ast-types": "^0.16.1", - "esprima": "~4.0.0", - "source-map": "~0.6.1", - "tiny-invariant": "^1.3.3", - "tslib": "^2.0.1" - }, - "engines": { - "node": ">= 4" - } - }, "node_modules/reflect.getprototypeof": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz", @@ -10096,27 +9642,21 @@ } }, "node_modules/shadcn-ui": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/shadcn-ui/-/shadcn-ui-0.8.0.tgz", - "integrity": "sha512-avqRgjJ6PIQQXdfvoCAWQpyLTLk6oHhtU5DQKmLeYcgu1ZIsgMqA9MKWAkr0HpEdCAenCCZvFbvJ2C2m5ZXRiA==", + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/shadcn-ui/-/shadcn-ui-0.2.3.tgz", + "integrity": "sha512-Bf2y8d5VusDbCs5l/SRmFTJ2tW9oBvIDWL1xmmURKuxn8rznXApoTdflPJ+Q68zKFXmIh6cYd4cJ8I8nzOkoAQ==", "dependencies": { "@antfu/ni": "^0.21.4", - "@babel/core": "^7.22.1", - "@babel/parser": "^7.22.6", - "@babel/plugin-transform-typescript": "^7.22.5", "chalk": "5.2.0", "commander": "^10.0.0", "cosmiconfig": "^8.1.3", "diff": "^5.1.0", "execa": "^7.0.0", - "fast-glob": "^3.3.2", "fs-extra": "^11.1.0", "https-proxy-agent": "^6.2.0", - "lodash.template": "^4.5.0", "node-fetch": "^3.3.0", "ora": "^6.1.2", "prompts": "^2.4.2", - "recast": "^0.23.2", "ts-morph": "^18.0.0", "tsconfig-paths": "^4.2.0", "zod": "^3.20.2" @@ -10324,14 +9864,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "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", @@ -10783,19 +10315,6 @@ "node": ">=0.8" } }, - "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-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==", - "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", @@ -10987,6 +10506,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", + "dev": true, "funding": [ { "type": "opencollective", @@ -11330,11 +10850,6 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" - }, "node_modules/yaml": { "version": "2.4.5", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.5.tgz", diff --git a/package.json b/package.json index 2c9b02c073..8bda832bff 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "react": "18.3.1", "react-dom": "18.3.1", "server-only": "^0.0.1", - "shadcn-ui": "^0.8.0", + "shadcn-ui": "^0.2.3", "tailwind-merge": "^2.4.0", "tailwindcss-animate": "^1.0.7" }, @@ -39,7 +39,7 @@ "eslint-config-next": "14.2.1", "eslint-config-prettier": "^9.1.0", "eslint-plugin-import": "^2.26.0", - "eslint-plugin-jsx-a11y": "^6.4.1", + "eslint-plugin-jsx-a11y": "^6.9.0", "eslint-plugin-node": "^11.1.0", "eslint-plugin-prettier": "^5.1.3", "eslint-plugin-react": "^7.23.2", From 092ad10c5671c460e82d3eceb10b996b614ae88f Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Tue, 13 Aug 2024 10:12:48 +0200 Subject: [PATCH 115/411] chore: add all icons for services --- components/icons/index.ts | 1 + components/icons/services/IconServices.tsx | 860 +++++++++++++++++++++ 2 files changed, 861 insertions(+) create mode 100644 components/icons/services/IconServices.tsx diff --git a/components/icons/index.ts b/components/icons/index.ts index 3929e06cd5..c7e74bdf98 100644 --- a/components/icons/index.ts +++ b/components/icons/index.ts @@ -3,3 +3,4 @@ export * from "./providers/AwsProvider"; export * from "./providers/AzureProvider"; export * from "./providers/GoogleCloudProvider"; export * from "./prowler/ProwlerIcons"; +export * from "./services/IconServices"; diff --git a/components/icons/services/IconServices.tsx b/components/icons/services/IconServices.tsx new file mode 100644 index 0000000000..739d21e9a9 --- /dev/null +++ b/components/icons/services/IconServices.tsx @@ -0,0 +1,860 @@ +import { IconSvgProps } from "@/types"; + +export const IAMAccessAnalyzerIcon: React.FC = ({ + size = 32, + width, + height, + className = "rounded-md", + ...props +}) => ( + + + + + + +); + +export const AWSAccountIcon: React.FC = ({ + size = 32, + width, + height, + className = "rounded-md", + ...props +}) => ( + + + + + + +); + +export const AWSCertificateManagerIcon: React.FC = ({ + size = 32, + width, + height, + className = "rounded-md", + ...props +}) => ( + + + + + + + + +); + +export const AWSAthenaIcon: React.FC = ({ + size = 32, + width, + height, + className = "rounded-md", + ...props +}) => ( + + + + + + +); + +export const AWSLambdaIcon: React.FC = ({ + size = 32, + width, + height, + className = "rounded-md", + ...props +}) => ( + + + + + + +); + +export const AWSCloudFormationIcon: React.FC = ({ + size = 32, + width, + height, + className = "rounded-md", + ...props +}) => ( + + + + + + +); + +export const AWSCloudTrailIcon: React.FC = ({ + size = 32, + width, + height, + className = "rounded-md", + ...props +}) => ( + + + + + + +); + +export const AWSCloudWatchIcon: React.FC = ({ + size = 32, + width, + height, + className = "rounded-md", + ...props +}) => ( + + + + + + +); + +export const AWSConfigIcon: React.FC = ({ + size = 32, + width, + height, + className = "rounded-md", + ...props +}) => ( + + + + + + +); + +export const AWSDatabaseMigrationServiceIcon: React.FC = ({ + size = 32, + width, + height, + className = "rounded-md", + ...props +}) => ( + + + + + + +); + +export const AmazonEC2Icon: React.FC = ({ + size = 32, + width, + height, + className = "rounded-md", + ...props +}) => ( + + + + + + +); + +export const AmazonEMRIcon: React.FC = ({ + size = 32, + width, + height, + className = "rounded-md", + ...props +}) => ( + + + + + + +); + +export const AWSGlueIcon: React.FC = ({ + size = 32, + width, + height, + className = "rounded-md", + ...props +}) => ( + + + + + + +); + +export const AmazonGuardDutyIcon: React.FC = ({ + size = 32, + width, + height, + className = "rounded-md", + ...props +}) => ( + + + + + + +); + +export const AWSIAMIcon: React.FC = ({ + size = 32, + width, + height, + className = "rounded-md", + ...props +}) => ( + + + + + + +); + +export const AmazonInspectorIcon: React.FC = ({ + size = 32, + width, + height, + className = "rounded-md", + ...props +}) => ( + + + + + + +); + +export const AmazonMacieIcon: React.FC = ({ + size = 32, + width, + height, + className = "rounded-md", + ...props +}) => ( + + + + + + +); + +export const AWSNetworkFirewallIcon: React.FC = ({ + size = 32, + width, + height, + className = "rounded-md", + ...props +}) => ( + + + + + + +); + +export const AWSOrganizationsIcon: React.FC = ({ + size = 32, + width, + height, + className = "rounded-md", + ...props +}) => ( + + + + + + +); + +export const AmazonRDSIcon: React.FC = ({ + size = 32, + width, + height, + className = "rounded-md", + ...props +}) => ( + + + + + + +); + +export const AWSResourceExplorerIcon: React.FC = ({ + size = 32, + width, + height, + className = "rounded-md", + ...props +}) => ( + + + + + + +); + +export const AmazonRoute53Icon: React.FC = ({ + size = 32, + width, + height, + className = "rounded-md", + ...props +}) => ( + + + + + + +); + +export const AmazonS3Icon: React.FC = ({ + size = 32, + width, + height, + className = "rounded-md", + ...props +}) => ( + + + + + + + + + + + +); + +export const AWSSecurityHubIcon: React.FC = ({ + size = 32, + width, + height, + className = "rounded-md", + ...props +}) => ( + + + + + + +); + +export const AmazonSNSIcon: React.FC = ({ + size = 32, + width, + height, + className = "rounded-md", + ...props +}) => ( + + + + + + +); + +export const AWSSystemsManagerIncidentManagerIcon: React.FC = ({ + size = 32, + width, + height, + className = "rounded-md", + ...props +}) => ( + + + + + + +); + +export const AWSTrustedAdvisorIcon: React.FC = ({ + size = 32, + width, + height, + className = "rounded-md", + ...props +}) => ( + + + + + + +); + +export const AmazonVPCIcon: React.FC = ({ + size = 32, + width, + height, + className = "rounded-md", + ...props +}) => ( + + + + + + +); From 891c17124781870d56edb4f4112a7ddb0b6cdcc0 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Wed, 14 Aug 2024 09:06:47 +0200 Subject: [PATCH 116/411] feat: add providers-badge component for global use and filter components --- components/filters/CustomAccountSelection.tsx | 31 +++++++ components/filters/CustomDatePicker.tsx | 64 +++++++++++++++ components/filters/CustomProviderInputs.tsx | 34 ++++++++ components/filters/CustomSelectProvider.tsx | 63 +++++++++++++++ components/filters/FilterControls.tsx | 13 +++ components/filters/index.ts | 5 ++ components/icons/index.ts | 3 - .../providers-badge/AWSProviderBadge.tsx | 42 ++++++++++ .../providers-badge/AzureProviderBadge.tsx | 80 +++++++++++++++++++ .../providers-badge/GCPProviderBadge.tsx | 42 ++++++++++ components/icons/providers-badge/index.ts | 3 + components/services/index.ts | 0 12 files changed, 377 insertions(+), 3 deletions(-) create mode 100644 components/filters/CustomAccountSelection.tsx create mode 100644 components/filters/CustomDatePicker.tsx create mode 100644 components/filters/CustomProviderInputs.tsx create mode 100644 components/filters/CustomSelectProvider.tsx create mode 100644 components/filters/FilterControls.tsx create mode 100644 components/filters/index.ts create mode 100644 components/icons/providers-badge/AWSProviderBadge.tsx create mode 100644 components/icons/providers-badge/AzureProviderBadge.tsx create mode 100644 components/icons/providers-badge/GCPProviderBadge.tsx create mode 100644 components/icons/providers-badge/index.ts create mode 100644 components/services/index.ts diff --git a/components/filters/CustomAccountSelection.tsx b/components/filters/CustomAccountSelection.tsx new file mode 100644 index 0000000000..d09052ad4d --- /dev/null +++ b/components/filters/CustomAccountSelection.tsx @@ -0,0 +1,31 @@ +"use client"; +import { Select, SelectItem } from "@nextui-org/react"; + +const accounts = [ + { key: "audit-test-1", label: "740350143844" }, + { key: "audit-test-2", label: "890837126756" }, + { key: "audit-test-3", label: "563829104923" }, + { key: "audit-test-4", label: "678943217543" }, + { key: "audit-test-5", label: "932187465320" }, + { key: "audit-test-6", label: "492837106587" }, + { key: "audit-test-7", label: "812736459201" }, + { key: "audit-test-8", label: "374829106524" }, + { key: "audit-test-9", label: "926481053298" }, + { key: "audit-test-10", label: "748192364579" }, + { key: "audit-test-11", label: "501374829106" }, +]; +export const CustomAccountSelection = () => { + return ( + + ); +}; diff --git a/components/filters/CustomDatePicker.tsx b/components/filters/CustomDatePicker.tsx new file mode 100644 index 0000000000..d9657b9892 --- /dev/null +++ b/components/filters/CustomDatePicker.tsx @@ -0,0 +1,64 @@ +"use client"; + +import { + getLocalTimeZone, + startOfMonth, + startOfWeek, + today, +} from "@internationalized/date"; +import { Button, ButtonGroup, DatePicker } from "@nextui-org/react"; +import { useDateFormatter, useLocale } from "@react-aria/i18n"; +import React from "react"; + +export const CustomDatePicker = () => { + const defaultDate = today(getLocalTimeZone()); + + const [value, setValue] = React.useState(defaultDate); + + const { locale } = useLocale(); + const formatter = useDateFormatter({ dateStyle: "full" }); + + const now = today(getLocalTimeZone()); + const nextWeek = startOfWeek(now.add({ weeks: 1 }), locale); + const nextMonth = startOfMonth(now.add({ months: 1 })); + + return ( +
+
} + CalendarTopContent={ + + + + + + } + calendarProps={{ + focusedValue: value, + onFocusChange: setValue, + nextButtonProps: { + variant: "bordered", + }, + prevButtonProps: { + variant: "bordered", + }, + }} + value={value} + onChange={setValue} + label="Scan date" + size="sm" + variant="flat" + /> +

+ Selected date:{" "} + {value ? formatter.format(value.toDate(getLocalTimeZone())) : "--"} +

+
+ ); +}; diff --git a/components/filters/CustomProviderInputs.tsx b/components/filters/CustomProviderInputs.tsx new file mode 100644 index 0000000000..d97b03b842 --- /dev/null +++ b/components/filters/CustomProviderInputs.tsx @@ -0,0 +1,34 @@ +import React from "react"; + +import { + AWSProviderBadge, + AzureProviderBadge, + GCPProviderBadge, +} from "../icons/providers-badge"; + +export const CustomProviderInputAWS = () => { + return ( +
+ +

Amazon Web Services

+
+ ); +}; + +export const CustomProviderInputAzure = () => { + return ( +
+ +

Azure

+
+ ); +}; + +export const CustomProviderInputGCP = () => { + return ( +
+ +

Google Cloud Platform

+
+ ); +}; diff --git a/components/filters/CustomSelectProvider.tsx b/components/filters/CustomSelectProvider.tsx new file mode 100644 index 0000000000..222793fdf8 --- /dev/null +++ b/components/filters/CustomSelectProvider.tsx @@ -0,0 +1,63 @@ +"use client"; + +import { Select, SelectItem } from "@nextui-org/react"; + +import { + CustomProviderInputAWS, + CustomProviderInputAzure, + CustomProviderInputGCP, +} from "./CustomProviderInputs"; + +const dataInputsProvider = [ + { + key: "aws", + label: "Amazon Web Services", + value: , + }, + { + key: "gcp", + label: "Google Cloud Platform", + value: , + }, + { + key: "azure", + label: "Microsoft Azure", + value: , + }, + { + key: "kubernetes", + label: "Kubernetes", + value: , + }, +]; + +export const CustomSelectProvider = () => { + return ( + + ); +}; diff --git a/components/filters/FilterControls.tsx b/components/filters/FilterControls.tsx new file mode 100644 index 0000000000..a3ed124159 --- /dev/null +++ b/components/filters/FilterControls.tsx @@ -0,0 +1,13 @@ +import { CustomAccountSelection } from "./CustomAccountSelection"; +import { CustomDatePicker } from "./CustomDatePicker"; +import { CustomSelectProvider } from "./CustomSelectProvider"; + +export const FilterControls = () => { + return ( +
+ + + +
+ ); +}; diff --git a/components/filters/index.ts b/components/filters/index.ts new file mode 100644 index 0000000000..ba1a6814a1 --- /dev/null +++ b/components/filters/index.ts @@ -0,0 +1,5 @@ +export * from "../filters/CustomAccountSelection"; +export * from "../filters/CustomDatePicker"; +export * from "../filters/CustomProviderInputs"; +export * from "../filters/CustomSelectProvider"; +export * from "../filters/FilterControls"; diff --git a/components/icons/index.ts b/components/icons/index.ts index c7e74bdf98..c057522527 100644 --- a/components/icons/index.ts +++ b/components/icons/index.ts @@ -1,6 +1,3 @@ export * from "./Icons"; -export * from "./providers/AwsProvider"; -export * from "./providers/AzureProvider"; -export * from "./providers/GoogleCloudProvider"; export * from "./prowler/ProwlerIcons"; export * from "./services/IconServices"; diff --git a/components/icons/providers-badge/AWSProviderBadge.tsx b/components/icons/providers-badge/AWSProviderBadge.tsx new file mode 100644 index 0000000000..f0dc91958e --- /dev/null +++ b/components/icons/providers-badge/AWSProviderBadge.tsx @@ -0,0 +1,42 @@ +import * as React from "react"; + +import { IconSvgProps } from "@/types"; + +export const AWSProviderBadge: React.FC = ({ + size, + width, + height, + ...props +}) => ( + +); diff --git a/components/icons/providers-badge/AzureProviderBadge.tsx b/components/icons/providers-badge/AzureProviderBadge.tsx new file mode 100644 index 0000000000..983e4e41df --- /dev/null +++ b/components/icons/providers-badge/AzureProviderBadge.tsx @@ -0,0 +1,80 @@ +import * as React from "react"; + +import { IconSvgProps } from "@/types"; + +export const AzureProviderBadge: React.FC = ({ + size, + width, + height, + ...props +}) => ( + +); diff --git a/components/icons/providers-badge/GCPProviderBadge.tsx b/components/icons/providers-badge/GCPProviderBadge.tsx new file mode 100644 index 0000000000..987a9fd074 --- /dev/null +++ b/components/icons/providers-badge/GCPProviderBadge.tsx @@ -0,0 +1,42 @@ +import * as React from "react"; + +import { IconSvgProps } from "@/types"; + +export const GCPProviderBadge: React.FC = ({ + size, + width, + height, + ...props +}) => ( + +); diff --git a/components/icons/providers-badge/index.ts b/components/icons/providers-badge/index.ts new file mode 100644 index 0000000000..ea5f196ef8 --- /dev/null +++ b/components/icons/providers-badge/index.ts @@ -0,0 +1,3 @@ +export * from "./AWSProviderBadge"; +export * from "./AzureProviderBadge"; +export * from "./GCPProviderBadge"; diff --git a/components/services/index.ts b/components/services/index.ts new file mode 100644 index 0000000000..e69de29bb2 From 8dba9a7d9ec0b5d9ad8619a83559d7f340cc2a03 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Wed, 14 Aug 2024 09:50:33 +0200 Subject: [PATCH 117/411] feat: add kubernetes as a supported provider --- .../providers-badge/KS8ProviderBadge.tsx | 32 ++++++++ components/icons/providers-badge/index.ts | 1 + components/icons/providers/AwsProvider.tsx | 42 ---------- components/icons/providers/AzureProvider.tsx | 80 ------------------- .../icons/providers/GoogleCloudProvider.tsx | 42 ---------- components/providers/ProviderInfo.tsx | 24 +++--- 6 files changed, 45 insertions(+), 176 deletions(-) create mode 100644 components/icons/providers-badge/KS8ProviderBadge.tsx delete mode 100644 components/icons/providers/AwsProvider.tsx delete mode 100644 components/icons/providers/AzureProvider.tsx delete mode 100644 components/icons/providers/GoogleCloudProvider.tsx diff --git a/components/icons/providers-badge/KS8ProviderBadge.tsx b/components/icons/providers-badge/KS8ProviderBadge.tsx new file mode 100644 index 0000000000..280b4d381c --- /dev/null +++ b/components/icons/providers-badge/KS8ProviderBadge.tsx @@ -0,0 +1,32 @@ +import * as React from "react"; + +import { IconSvgProps } from "@/types"; + +export const KS8ProviderBadge: React.FC = ({ + size, + width, + height, + ...props +}) => ( + +); diff --git a/components/icons/providers-badge/index.ts b/components/icons/providers-badge/index.ts index ea5f196ef8..8aa68424a0 100644 --- a/components/icons/providers-badge/index.ts +++ b/components/icons/providers-badge/index.ts @@ -1,3 +1,4 @@ export * from "./AWSProviderBadge"; export * from "./AzureProviderBadge"; export * from "./GCPProviderBadge"; +export * from "./KS8ProviderBadge"; diff --git a/components/icons/providers/AwsProvider.tsx b/components/icons/providers/AwsProvider.tsx deleted file mode 100644 index 48cdfe377d..0000000000 --- a/components/icons/providers/AwsProvider.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import * as React from "react"; - -import { IconSvgProps } from "@/types"; - -export const AwsProvider: React.FC = ({ - size, - width, - height, - ...props -}) => ( - -); diff --git a/components/icons/providers/AzureProvider.tsx b/components/icons/providers/AzureProvider.tsx deleted file mode 100644 index 1f20189bbf..0000000000 --- a/components/icons/providers/AzureProvider.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import * as React from "react"; - -import { IconSvgProps } from "@/types"; - -export const AzureProvider: React.FC = ({ - size, - width, - height, - ...props -}) => ( - -); diff --git a/components/icons/providers/GoogleCloudProvider.tsx b/components/icons/providers/GoogleCloudProvider.tsx deleted file mode 100644 index 0fa5bde7ae..0000000000 --- a/components/icons/providers/GoogleCloudProvider.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import * as React from "react"; - -import { IconSvgProps } from "@/types"; - -export const GoogleCloudProvider: React.FC = ({ - size, - width, - height, - ...props -}) => ( - -); diff --git a/components/providers/ProviderInfo.tsx b/components/providers/ProviderInfo.tsx index eb5962d530..8730e6cfcd 100644 --- a/components/providers/ProviderInfo.tsx +++ b/components/providers/ProviderInfo.tsx @@ -1,17 +1,16 @@ import React from "react"; +import { WifiIcon, WifiOffIcon, WifiPendingIcon } from "../icons"; import { - AwsProvider, - AzureProvider, - GoogleCloudProvider, - WifiIcon, - WifiOffIcon, - WifiPendingIcon, -} from "../icons"; + AWSProviderBadge, + AzureProviderBadge, + GCPProviderBadge, + KS8ProviderBadge, +} from "../icons/providers-badge"; interface ProviderInfoProps { connected: boolean | null; - provider: "aws" | "azure" | "gcp"; + provider: "aws" | "azure" | "gcp" | "kubernetes"; providerAlias: string; providerId: string; } @@ -37,12 +36,13 @@ export const ProviderInfo: React.FC = ({ const getProviderLogo = () => { switch (provider) { case "aws": - return ; + return ; case "azure": - return ; - + return ; case "gcp": - return ; + return ; + case "kubernetes": + return ; default: return null; } From 4c0a14b96cbb697b06764c8023cd6e5d0405222b Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Wed, 14 Aug 2024 09:52:03 +0200 Subject: [PATCH 118/411] chore: update components with the new paths --- app/(prowler)/services/page.tsx | 63 +++++++++++++++++++- components/providers/CustomRadioProvider.tsx | 9 +-- 2 files changed, 65 insertions(+), 7 deletions(-) diff --git a/app/(prowler)/services/page.tsx b/app/(prowler)/services/page.tsx index 77c6de68ec..1564675110 100644 --- a/app/(prowler)/services/page.tsx +++ b/app/(prowler)/services/page.tsx @@ -1,5 +1,34 @@ -import React from "react"; - +import { FilterControls } from "@/components/filters"; +import { + AmazonEC2Icon, + AmazonEMRIcon, + AmazonGuardDutyIcon, + AmazonInspectorIcon, + AmazonMacieIcon, + AmazonRDSIcon, + AmazonRoute53Icon, + AmazonS3Icon, + AmazonSNSIcon, + AmazonVPCIcon, + AWSAccountIcon, + AWSAthenaIcon, + AWSCertificateManagerIcon, + AWSCloudFormationIcon, + AWSCloudTrailIcon, + AWSCloudWatchIcon, + AWSConfigIcon, + AWSDatabaseMigrationServiceIcon, + AWSGlueIcon, + AWSIAMIcon, + AWSLambdaIcon, + AWSNetworkFirewallIcon, + AWSOrganizationsIcon, + AWSResourceExplorerIcon, + AWSSecurityHubIcon, + AWSSystemsManagerIncidentManagerIcon, + AWSTrustedAdvisorIcon, + IAMAccessAnalyzerIcon, +} from "@/components/icons"; import { Header } from "@/components/ui"; export default function Services() { @@ -9,8 +38,36 @@ export default function Services() { title="Services" icon="material-symbols:linked-services-outline" /> + -

Hi hi from Services page

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + ); } diff --git a/components/providers/CustomRadioProvider.tsx b/components/providers/CustomRadioProvider.tsx index f40ac37056..05c9926b0a 100644 --- a/components/providers/CustomRadioProvider.tsx +++ b/components/providers/CustomRadioProvider.tsx @@ -4,7 +4,8 @@ import { UseRadioProps } from "@nextui-org/radio/dist/use-radio"; import { cn, RadioGroup, useRadio, VisuallyHidden } from "@nextui-org/react"; import React from "react"; -import { AwsProvider, AzureProvider, GoogleCloudProvider } from "../icons"; +import { AWSProviderBadge, AzureProviderBadge } from "../icons/providers-badge"; +import { GCPProviderBadge } from "../icons/providers-badge/GCPProviderBadge"; interface CustomRadioProps extends UseRadioProps { description?: string; @@ -56,19 +57,19 @@ export const CustomRadioProvider = () => {
- + AWS
- + GCP
- + Azure
From afc4189577a9d823d7dd6abc65da9f965092e90e Mon Sep 17 00:00:00 2001 From: Sophia Dao Date: Wed, 14 Aug 2024 08:06:27 -0500 Subject: [PATCH 119/411] Findings Page - Findings Card Components (#37) --- app/(prowler)/findings/page.tsx | 44 +++--- app/(prowler)/layout.tsx | 3 +- components/findings/FindingsCard.tsx | 93 ++++++++++++ components/findings/FindingsCardContent.tsx | 27 ++++ components/findings/FindingsCardDetail.tsx | 59 ++++++++ components/findings/FindingsCardScan.tsx | 25 ++++ components/findings/FindingsCardType.tsx | 25 ++++ components/findings/index.ts | 6 +- .../findings/table/DataTableFindings.tsx | 12 +- components/findings/table/FindingsCard.tsx | 134 ------------------ .../providers/table/ColumnsProvider.tsx | 2 +- .../providers/table/DataTableProvider.tsx | 2 +- components/ui/table/SeverityBadge.tsx | 55 +++---- components/ui/table/StatusBadge.tsx | 31 ++-- types/components.ts | 2 +- 15 files changed, 311 insertions(+), 209 deletions(-) create mode 100644 components/findings/FindingsCard.tsx create mode 100644 components/findings/FindingsCardContent.tsx create mode 100644 components/findings/FindingsCardDetail.tsx create mode 100644 components/findings/FindingsCardScan.tsx create mode 100644 components/findings/FindingsCardType.tsx delete mode 100644 components/findings/table/FindingsCard.tsx diff --git a/app/(prowler)/findings/page.tsx b/app/(prowler)/findings/page.tsx index c551a3cb9f..5ef11d41ed 100644 --- a/app/(prowler)/findings/page.tsx +++ b/app/(prowler)/findings/page.tsx @@ -49,8 +49,8 @@ const SSRDataTable = async () => { checkId: "cloudformation_stack_outputs_find_secrets", checkLink: "https://app.prowler.pro/app/findings?date=2024-08-05&search=cloudformation_stack_outputs_find_secrets", - type: "Not applicable", - scanTime: "2024-08-05 @ 14:22:00 UTC", + type: ["Not applicable"], + scanTime: "2024-07-17T09:55:14.191475Z", findingId: "ba123291-03a5-49a1-b962-6fdb1d2b9c9b", findingLink: "https://app.prowler.pro/app/findings?date=2024-08-05&search=ba123291-03a5-49a1-b962-6fdb1d2b9c9b", @@ -87,8 +87,12 @@ const SSRDataTable = async () => { checkId: "iam_root_mfa_enabled", checkLink: "https://app.prowler.pro/app/findings?search=iam_root_mfa_enabled", - type: "Software and Configuration Checks, Industry and Regulatory Standards, CIS AWS Foundations Benchmark", - scanTime: "2024-08-05 @ 14:22:00 UTC", + type: [ + "Software and Configuration Checks", + "Industry and Regulatory Standards", + "CIS AWS Foundations Benchmark", + ], + scanTime: "2024-07-17T09:55:14.191475Z", findingId: "bc3a34e0-16f0-4ea1-ac62-f796c8af3448", findingLink: "https://app.prowler.pro/app/findings?date=2024-08-05&search=bc3a34e0-16f0-4ea1-ac62-f796c8af3448", @@ -123,8 +127,8 @@ const SSRDataTable = async () => { checkId: "s3_bucket_public_access", checkLink: "https://app.prowler.pro/app/findings?date=2024-08-06&search=s3_bucket_public_access", - type: "Security", - scanTime: "2024-08-06 @ 10:45:00 UTC", + type: ["Security"], + scanTime: "2024-07-17T09:55:14.191475Z", findingId: "e7b3d6a2-39a1-4f0e-9b78-29f4e6f292d1", findingLink: "https://app.prowler.pro/app/findings?date=2024-08-06&search=e7b3d6a2-39a1-4f0e-9b78-29f4e6f292d1", @@ -162,8 +166,8 @@ const SSRDataTable = async () => { checkId: "iam_password_policy_min_length", checkLink: "https://app.prowler.pro/app/findings?date=2024-08-06&search=iam_password_policy_min_length", - type: "Security", - scanTime: "2024-08-06 @ 11:10:00 UTC", + type: ["Security"], + scanTime: "2024-07-17T09:55:14.191475Z", findingId: "c2b3d1a4-7a9f-4d5c-a9ef-765a1d7f421c", findingLink: "https://app.prowler.pro/app/findings?date=2024-08-06&search=c2b3d1a4-7a9f-4d5c-a9ef-765a1d7f421c", @@ -201,8 +205,8 @@ const SSRDataTable = async () => { checkId: "rds_instance_public_access", checkLink: "https://app.prowler.pro/app/findings?date=2024-08-06&search=rds_instance_public_access", - type: "Security", - scanTime: "2024-08-06 @ 13:15:00 UTC", + type: ["Security"], + scanTime: "2024-07-17T09:55:14.191475Z", findingId: "f3b4c5d6-19a7-45d8-bc3e-8c5f6a7d8e9b", findingLink: "https://app.prowler.pro/app/findings?date=2024-08-06&search=f3b4c5d6-19a7-45d8-bc3e-8c5f6a7d8e9b", @@ -240,8 +244,8 @@ const SSRDataTable = async () => { checkId: "ebs_volume_encryption", checkLink: "https://app.prowler.pro/app/findings?date=2024-08-07&search=ebs_volume_encryption", - type: "Encryption", - scanTime: "2024-08-07 @ 09:30:00 UTC", + type: ["Encryption"], + scanTime: "2024-07-17T09:55:14.191475Z", findingId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890", findingLink: "https://app.prowler.pro/app/findings?date=2024-08-07&search=a1b2c3d4-e5f6-7890-abcd-ef1234567890", @@ -278,8 +282,8 @@ const SSRDataTable = async () => { checkId: "cloudtrail_all_regions_enabled", checkLink: "https://app.prowler.pro/app/findings?date=2024-08-07&search=cloudtrail_all_regions_enabled", - type: "Logging", - scanTime: "2024-08-07 @ 11:00:00 UTC", + type: ["Logging"], + scanTime: "2024-07-17T09:55:14.191475Z", findingId: "b2c3d4e5-f6a7-8901-bcde-f123456789ab", findingLink: "https://app.prowler.pro/app/findings?date=2024-08-07&search=b2c3d4e5-f6a7-8901-bcde-f123456789ab", @@ -317,8 +321,8 @@ const SSRDataTable = async () => { checkId: "ec2_instance_outdated_ami", checkLink: "https://app.prowler.pro/app/findings?date=2024-08-07&search=ec2_instance_outdated_ami", - type: "Configuration", - scanTime: "2024-08-07 @ 12:45:00 UTC", + type: ["Configuration"], + scanTime: "2024-07-17T09:55:14.191475Z", findingId: "c3d4e5f6-a789-0123-bcde-f234567890ab", findingLink: "https://app.prowler.pro/app/findings?date=2024-08-07&search=c3d4e5f6-a789-0123-bcde-f234567890ab", @@ -357,8 +361,8 @@ const SSRDataTable = async () => { checkId: "cloudwatch_alarms_for_critical_events", checkLink: "https://app.prowler.pro/app/findings?date=2024-08-08&search=cloudwatch_alarms_for_critical_events", - type: "Monitoring", - scanTime: "2024-08-08 @ 09:50:00 UTC", + type: ["Monitoring"], + scanTime: "2024-07-17T09:55:14.191475Z", findingId: "d4e5f6a7-8901-bcde-f345678901ab", findingLink: "https://app.prowler.pro/app/findings?date=2024-08-08&search=d4e5f6a7-8901-bcde-f345678901ab", @@ -396,8 +400,8 @@ const SSRDataTable = async () => { checkId: "iam_user_least_privilege", checkLink: "https://app.prowler.pro/app/findings?date=2024-08-08&search=iam_user_least_privilege", - type: "Security", - scanTime: "2024-08-08 @ 10:15:00 UTC", + type: ["Security"], + scanTime: "2024-07-17T09:55:14.191475Z", findingId: "e5f6a7b8-9012-bcde-f456789012ab", findingLink: "https://app.prowler.pro/app/findings?date=2024-08-08&search=e5f6a7b8-9012-bcde-f456789012ab", diff --git a/app/(prowler)/layout.tsx b/app/(prowler)/layout.tsx index ec533b5892..0faab3f593 100644 --- a/app/(prowler)/layout.tsx +++ b/app/(prowler)/layout.tsx @@ -3,8 +3,7 @@ import "@/styles/globals.css"; import { Metadata, Viewport } from "next"; import React from "react"; -import { Toaster } from "@/components/ui"; -import { SidebarWrap } from "@/components/ui/sidebar"; +import { SidebarWrap, Toaster } from "@/components/ui"; import { fontSans } from "@/config/fonts"; import { siteConfig } from "@/config/site"; import { cn } from "@/lib/utils"; diff --git a/components/findings/FindingsCard.tsx b/components/findings/FindingsCard.tsx new file mode 100644 index 0000000000..cec81ffb54 --- /dev/null +++ b/components/findings/FindingsCard.tsx @@ -0,0 +1,93 @@ +import { Divider } from "@nextui-org/react"; +import clsx from "clsx"; +import React from "react"; + +import { + FindingsCardContent, + FindingsCardDetail, + FindingsCardScan, + FindingsCardType, +} from "@/components/findings"; +import { FindingProps } from "@/types"; +interface FindingsCardProps { + selectedRow: FindingProps; +} + +export const FindingsCard: React.FC = ({ selectedRow }) => { + const { attributes, card } = selectedRow || {}; + const { CheckTitle } = attributes || {}; + const { + resourceLink, + resourceId, + resourceARN, + checkLink, + checkId, + type, + scanTime, + findingLink, + findingId, + details, + riskDetails, + riskLink, + recommendationDetails, + recommendationLink, + referenceInformation, + referenceLink, + } = card || {}; + + return ( + <> +
+

{CheckTitle}

+ + + + + + + + + + + + + +
+ + ); +}; diff --git a/components/findings/FindingsCardContent.tsx b/components/findings/FindingsCardContent.tsx new file mode 100644 index 0000000000..f0f0f7becf --- /dev/null +++ b/components/findings/FindingsCardContent.tsx @@ -0,0 +1,27 @@ +import { Link } from "@nextui-org/react"; +import React from "react"; + +interface FindingsCardContentProps { + title: string; + url?: string; + description: string; +} + +export const FindingsCardContent: React.FC = ({ + title, + url, + description, +}) => { + return ( + <> +

{title}

+ {url ? ( + + {description} + + ) : ( +

{description}

+ )} + + ); +}; diff --git a/components/findings/FindingsCardDetail.tsx b/components/findings/FindingsCardDetail.tsx new file mode 100644 index 0000000000..5b3ffc59bd --- /dev/null +++ b/components/findings/FindingsCardDetail.tsx @@ -0,0 +1,59 @@ +import { Button, Link } from "@nextui-org/react"; +import React from "react"; + +interface FindingsCardDetailProps { + title: string; + url?: string; + description: string; + type?: DetailType; +} + +type DetailType = "default" | "risk" | "recommendation" | "reference"; + +const getDetailColorClass = (type: DetailType): string => { + switch (type) { + case "risk": + return "border-red-200"; + case "recommendation": + return "border-green-200"; + case "reference": + return "border-gray-200"; + case "default": + default: + return "border-yellow-200"; + } +}; + +export const FindingsCardDetail: React.FC = ({ + title, + url, + description, + type = "default", +}) => { + return ( + <> + {description && ( +
+

+ {title} + {url && ( + + )} +

+

{description}

+
+ )} + + ); +}; diff --git a/components/findings/FindingsCardScan.tsx b/components/findings/FindingsCardScan.tsx new file mode 100644 index 0000000000..b4dd7bd656 --- /dev/null +++ b/components/findings/FindingsCardScan.tsx @@ -0,0 +1,25 @@ +import { format, parseISO } from "date-fns"; +import React from "react"; + +interface FindingsCardScanProps { + title: string; + dateTime: string; +} + +export const FindingsCardScan: React.FC = ({ + title, + dateTime = "", +}) => { + const date = dateTime && parseISO(dateTime); + const formattedDate = date && format(date, "MMM dd, yyyy"); + const formattedTime = date && format(date, "p 'UTC'"); + + return ( + <> +

{title}

+

+ {formattedDate} at {formattedTime} +

+ + ); +}; diff --git a/components/findings/FindingsCardType.tsx b/components/findings/FindingsCardType.tsx new file mode 100644 index 0000000000..e98fca6f1f --- /dev/null +++ b/components/findings/FindingsCardType.tsx @@ -0,0 +1,25 @@ +import React from "react"; + +interface FindingsCardTypeProps { + type: string[]; +} + +export const FindingsCardType: React.FC = ({ + type = [], +}) => { + const typeContent = () => { + if (type.length > 0) { + return type.join(", "); + } + return type[0]; + }; + + return ( + <> +

+ {type.length > 1 ? "Types:" : "Type:"} +

+

{typeContent()}

+ + ); +}; diff --git a/components/findings/index.ts b/components/findings/index.ts index 20c1920f4e..e7c1764519 100644 --- a/components/findings/index.ts +++ b/components/findings/index.ts @@ -1,4 +1,8 @@ +export * from "./FindingsCard"; +export * from "./FindingsCardContent"; +export * from "./FindingsCardDetail"; +export * from "./FindingsCardScan"; +export * from "./FindingsCardType"; export * from "./table/ColumnsFindings"; export * from "./table/DataTableFindings"; -export * from "./table/FindingsCard"; export * from "./table/SkeletonTableFindings"; diff --git a/components/findings/table/DataTableFindings.tsx b/components/findings/table/DataTableFindings.tsx index 5004dc2e6d..1c1fa7da9c 100644 --- a/components/findings/table/DataTableFindings.tsx +++ b/components/findings/table/DataTableFindings.tsx @@ -9,8 +9,9 @@ import { Row, useReactTable, } from "@tanstack/react-table"; +import clsx from "clsx"; -import { FindingsCard } from "@/components/findings/table/FindingsCard"; +import { FindingsCard } from "@/components/findings"; import { Table, TableBody, @@ -18,7 +19,7 @@ import { TableHead, TableHeader, TableRow, -} from "@/components/ui/table/Table"; +} from "@/components/ui"; import { FindingProps } from "@/types"; interface DataTableFindingsProps { @@ -48,7 +49,12 @@ export function DataTableFindings({ return ( <>
-
+
diff --git a/components/findings/table/FindingsCard.tsx b/components/findings/table/FindingsCard.tsx deleted file mode 100644 index 22056b29eb..0000000000 --- a/components/findings/table/FindingsCard.tsx +++ /dev/null @@ -1,134 +0,0 @@ -import { Button, Divider, Link } from "@nextui-org/react"; -import React from "react"; - -import { FindingProps } from "@/types"; -interface FindingsCardProps { - selectedRow: FindingProps; -} - -export const FindingsCard: React.FC = ({ selectedRow }) => { - const { attributes, card } = selectedRow || {}; - const { CheckTitle } = attributes || {}; - const { - resourceLink, - resourceId, - resourceARN, - checkLink, - checkId, - type, - scanTime, - findingLink, - findingId, - details, - riskDetails, - riskLink, - recommendationDetails, - recommendationLink, - referenceInformation, - referenceLink, - } = card || {}; - - return ( - <> -
-

{CheckTitle}

- -
-

Resource ID:

- - {resourceId} - -

Resource ARN:

-

{resourceARN}

-

Check ID:

- - {checkId} - -

Type:

-

{type}

-

Scan Time:

-

{scanTime}

-

Prowler Finding ID:

- - {findingId} - -
- - {details && ( -
-

Details:

-

{details}

-
- )} - - {riskDetails && ( -
-

- Risk: - {riskLink && ( - - )} -

-

{riskDetails}

-
- )} - - {recommendationDetails && ( -
-
- Recommendation: - {recommendationLink && ( - - )} -
-

{recommendationDetails}

-
- )} - - {referenceInformation && ( -
-
- Reference Information: - {referenceLink && ( - - )} -
-

{referenceInformation}

-
- )} -
- - ); -}; diff --git a/components/providers/table/ColumnsProvider.tsx b/components/providers/table/ColumnsProvider.tsx index 663c076477..82fe4992e2 100644 --- a/components/providers/table/ColumnsProvider.tsx +++ b/components/providers/table/ColumnsProvider.tsx @@ -11,7 +11,7 @@ import { ColumnDef } from "@tanstack/react-table"; import { add } from "date-fns"; import { VerticalDotsIcon } from "@/components/icons"; -import { StatusBadge } from "@/components/ui/table/StatusBadge"; +import { StatusBadge } from "@/components/ui"; import { ProviderProps } from "@/types"; import { CheckConnectionProvider } from "../CheckConnectionProvider"; diff --git a/components/providers/table/DataTableProvider.tsx b/components/providers/table/DataTableProvider.tsx index 172af2d891..2a037faf4f 100644 --- a/components/providers/table/DataTableProvider.tsx +++ b/components/providers/table/DataTableProvider.tsx @@ -18,7 +18,7 @@ import { TableHead, TableHeader, TableRow, -} from "@/components/ui/table/Table"; +} from "@/components/ui"; import { MetaDataProps } from "@/types"; import { DataTablePagination } from "./DataTablePagination"; diff --git a/components/ui/table/SeverityBadge.tsx b/components/ui/table/SeverityBadge.tsx index 7d9760c22e..2ce9d9e65c 100644 --- a/components/ui/table/SeverityBadge.tsx +++ b/components/ui/table/SeverityBadge.tsx @@ -1,46 +1,47 @@ import { Chip } from "@nextui-org/react"; +import clsx from "clsx"; import React from "react"; import { AlertIcon } from "@/components/icons"; type Severity = "critical" | "high" | "medium" | "low"; -const severityColorMap: Record< - Severity, - | "text-white bg-red-800" - | "text-white bg-red-600" - | "text-white bg-orange-500" - | "bg-yellow-200" -> = { - critical: "text-white bg-red-800", - high: "text-white bg-red-600", - medium: "text-white bg-orange-500", - low: "bg-yellow-200", -}; - -const severityIconMap: Partial> = { +const severityIconMap = { critical: , +} as const; + +const getSeverityColor = ( + severity: Severity, +): "danger" | "warning" | "default" => { + switch (severity) { + case "critical": + return "danger"; + case "high": + return "danger"; + case "medium": + return "warning"; + case "low": + return "default"; + default: + return "default"; // this is a fallback, though unnecessary due to typing + } }; -const getSeverityColor: (severity: Severity) => string = (severity) => - // eslint-disable-next-line security/detect-object-injection - severityColorMap[severity]; - -const getSeverityIcon: (severity: Severity) => React.ReactNode | null = ( - severity, -) => - // eslint-disable-next-line security/detect-object-injection - severityIconMap[severity] || null; +const getSeverityIcon = (severity: Severity): React.ReactNode | null => { + return severity === "critical" ? severityIconMap.critical : null; +}; export const SeverityBadge = ({ severity }: { severity: Severity }) => { + const color = getSeverityColor(severity); + return ( {severity} diff --git a/components/ui/table/StatusBadge.tsx b/components/ui/table/StatusBadge.tsx index d696e89ccf..c1f667f26e 100644 --- a/components/ui/table/StatusBadge.tsx +++ b/components/ui/table/StatusBadge.tsx @@ -11,32 +11,25 @@ type Status = const statusColorMap: Record< Status, - | "text-white bg-green-600" - | "bg-yellow-200" - | "text-white bg-red-600" - | "bg-gray-300" + "danger" | "warning" | "success" | "default" > = { - completed: "text-white bg-green-600", - pending: "bg-yellow-200", - cancelled: "text-white bg-red-600", - fail: "text-white bg-red-600", - success: "text-white bg-green-600", - muted: "bg-gray-300", + completed: "success", + pending: "warning", + cancelled: "danger", + fail: "danger", + success: "success", + muted: "default", }; -const getStatusColor: (status: Status) => string = (status) => - // eslint-disable-next-line security/detect-object-injection - statusColorMap[status]; - export const StatusBadge = ({ status }: { status: Status }) => { + const color = statusColorMap[status as keyof typeof statusColorMap]; + return ( {status} diff --git a/types/components.ts b/types/components.ts index 3d8a49393d..5580d151eb 100644 --- a/types/components.ts +++ b/types/components.ts @@ -58,7 +58,7 @@ export interface FindingProps { resourceARN: string; checkId: string; checkLink: string; - type: string; + type: string[]; scanTime: string; findingId: string; findingLink: string; From ebd3bb386c0850037acd70af5fe9632c735d9f0f Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Wed, 14 Aug 2024 15:21:52 +0200 Subject: [PATCH 120/411] feat: mock the API for services page and creating components --- actions/services.ts | 39 ++ app/(prowler)/providers/page.tsx | 2 +- app/(prowler)/services/page.tsx | 98 ++-- app/api/services/route.ts | 10 + components/icons/Icons.tsx | 44 ++ components/icons/services/IconServices.tsx | 63 +++ components/services/CardService.tsx | 45 ++ components/services/index.ts | 1 + dataServices.json | 494 +++++++++++++++++++++ 9 files changed, 733 insertions(+), 63 deletions(-) create mode 100644 actions/services.ts create mode 100644 app/api/services/route.ts create mode 100644 components/services/CardService.tsx create mode 100644 dataServices.json diff --git a/actions/services.ts b/actions/services.ts new file mode 100644 index 0000000000..aeb9d7de07 --- /dev/null +++ b/actions/services.ts @@ -0,0 +1,39 @@ +"use server"; + +import { revalidatePath } from "next/cache"; +import { redirect } from "next/navigation"; + +import { parseStringify } from "@/lib"; + +export const getService = async ({ page = 1 }) => { + if (isNaN(Number(page)) || page < 1) redirect("/services"); + const keyServer = process.env.LOCAL_SITE_URL; + + try { + const services = await fetch( + `${keyServer}/api/services?page%5Bnumber%5D=${page}`, + ); + const data = await services.json(); + const parsedData = parseStringify(data); + revalidatePath("/services"); + return parsedData; + } catch (error) { + console.error("Error fetching services:", error); + return undefined; + } +}; + +export const getErrorMessage = (error: unknown): string => { + let message: string; + + if (error instanceof Error) { + message = error.message; + } else if (error && typeof error === "object" && "message" in error) { + message = String(error.message); + } else if (typeof error === "string") { + message = error; + } else { + message = "Wops! Something when wrong."; + } + return message; +}; diff --git a/app/(prowler)/providers/page.tsx b/app/(prowler)/providers/page.tsx index 2c79e94c79..780d736710 100644 --- a/app/(prowler)/providers/page.tsx +++ b/app/(prowler)/providers/page.tsx @@ -16,7 +16,7 @@ export default async function Providers({ searchParams }: searchParamsProps) { return ( <>
- +
diff --git a/app/(prowler)/services/page.tsx b/app/(prowler)/services/page.tsx index 1564675110..e9af97846a 100644 --- a/app/(prowler)/services/page.tsx +++ b/app/(prowler)/services/page.tsx @@ -1,73 +1,47 @@ -import { FilterControls } from "@/components/filters"; -import { - AmazonEC2Icon, - AmazonEMRIcon, - AmazonGuardDutyIcon, - AmazonInspectorIcon, - AmazonMacieIcon, - AmazonRDSIcon, - AmazonRoute53Icon, - AmazonS3Icon, - AmazonSNSIcon, - AmazonVPCIcon, - AWSAccountIcon, - AWSAthenaIcon, - AWSCertificateManagerIcon, - AWSCloudFormationIcon, - AWSCloudTrailIcon, - AWSCloudWatchIcon, - AWSConfigIcon, - AWSDatabaseMigrationServiceIcon, - AWSGlueIcon, - AWSIAMIcon, - AWSLambdaIcon, - AWSNetworkFirewallIcon, - AWSOrganizationsIcon, - AWSResourceExplorerIcon, - AWSSecurityHubIcon, - AWSSystemsManagerIncidentManagerIcon, - AWSTrustedAdvisorIcon, - IAMAccessAnalyzerIcon, -} from "@/components/icons"; -import { Header } from "@/components/ui"; +import { Spacer } from "@nextui-org/react"; +import { redirect } from "next/navigation"; +import { Suspense } from "react"; -export default function Services() { +import { getService } from "@/actions/services"; +import { FilterControls } from "@/components/filters"; +import { SkeletonTableProvider } from "@/components/providers"; +import { CardService } from "@/components/services"; +import { Header } from "@/components/ui"; +import { searchParamsProps } from "@/types"; + +export default async function Services({ searchParams }: searchParamsProps) { return ( <>
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + }> + + ); } + +const SSRServiceGrid = async ({ searchParams }: searchParamsProps) => { + const page = searchParams.page ? parseInt(searchParams.page) : 1; + const servicesData = await getService({ page }); + const [services] = await Promise.all([servicesData]); + + if (services?.errors) redirect("/services"); + + return ( +
+ {services.services?.data.map((service: any) => ( + + ))} +
+ ); +}; diff --git a/app/api/services/route.ts b/app/api/services/route.ts new file mode 100644 index 0000000000..dbd490c239 --- /dev/null +++ b/app/api/services/route.ts @@ -0,0 +1,10 @@ +import { NextResponse } from "next/server"; + +import data from "../../../dataServices.json"; + +export async function GET() { + // Simulate fetching data with a delay + await new Promise((resolve) => setTimeout(resolve, 2000)); + + return NextResponse.json({ services: data }); +} diff --git a/components/icons/Icons.tsx b/components/icons/Icons.tsx index a929e7f750..6024849b2d 100644 --- a/components/icons/Icons.tsx +++ b/components/icons/Icons.tsx @@ -440,3 +440,47 @@ export const AlertIcon: React.FC = ({ ); + +export const NotificationIcon: React.FC = ({ + size = 24, + width, + height, + ...props +}) => ( + + + +); + +export const SuccessIcon: React.FC = ({ + size = 24, + width, + height, + ...props +}) => ( + + + +); diff --git a/components/icons/services/IconServices.tsx b/components/icons/services/IconServices.tsx index 739d21e9a9..2fd943604e 100644 --- a/components/icons/services/IconServices.tsx +++ b/components/icons/services/IconServices.tsx @@ -858,3 +858,66 @@ export const AmazonVPCIcon: React.FC = ({ ); + +export const getAWSIcon = (serviceAlias: string) => { + switch (serviceAlias) { + case "Amazon EC2": + return ; + case "Amazon EMR": + return ; + case "Amazon GuardDuty": + return ; + case "Amazon Inspector": + return ; + case "Amazon Macie": + return ; + case "Amazon RDS": + return ; + case "Amazon Route 53": + return ; + case "Amazon S3": + return ; + case "Amazon SNS": + return ; + case "Amazon VPC": + return ; + case "AWS Account": + return ; + case "AWS Athena": + return ; + case "AWS Certificate Manager": + return ; + case "AWS CloudFormation": + return ; + case "AWS CloudTrail": + return ; + case "AWS CloudWatch": + return ; + case "AWS Config": + return ; + case "AWS Database Migration Service": + return ; + case "AWS Glue": + return ; + case "AWS IAM": + return ; + case "AWS Lambda": + return ; + case "AWS Network Firewall": + return ; + case "AWS Organizations": + return ; + case "AWS Resource Explorer": + return ; + case "AWS Security Hub": + return ; + case "AWS Systems Manager Incident Manager": + return ; + case "AWS Trusted Advisor": + return ; + case "IAM Access Analyzer": + return ; + default: + return null; + } +}; diff --git a/components/services/CardService.tsx b/components/services/CardService.tsx new file mode 100644 index 0000000000..eaae8dc450 --- /dev/null +++ b/components/services/CardService.tsx @@ -0,0 +1,45 @@ +import { Card, CardBody, Chip } from "@nextui-org/react"; + +import { getAWSIcon, NotificationIcon, SuccessIcon } from "../icons"; + +interface CardServiceProps { + fidingsFailed: number; + serviceAlias: string; +} +export const CardService: React.FC = ({ + fidingsFailed, + serviceAlias, +}) => { + return ( + + + {getAWSIcon(serviceAlias)} +
+

{serviceAlias}

+ + {fidingsFailed > 0 + ? `${fidingsFailed} Failed Findings` + : "All findings passed"} + +
+ + 0 ? ( + + ) : ( + + ) + } + color={fidingsFailed > 0 ? "danger" : "success"} + radius="full" + size="md" + > + {fidingsFailed > 0 ? fidingsFailed : "All passed"} + +
+
+ ); +}; diff --git a/components/services/index.ts b/components/services/index.ts index e69de29bb2..ea59ab0d9b 100644 --- a/components/services/index.ts +++ b/components/services/index.ts @@ -0,0 +1 @@ +export * from "./CardService"; diff --git a/dataServices.json b/dataServices.json new file mode 100644 index 0000000000..cfda95d520 --- /dev/null +++ b/dataServices.json @@ -0,0 +1,494 @@ +{ + "links": { + "first": "http://localhost:8080/api/v1/services?page%5Bnumber%5D=1", + "last": "http://localhost:8080/api/v1/services?page%5Bnumber%5D=1", + "next": null, + "prev": null + }, + "data": [ + { + "type": "Service", + "id": "001", + "attributes": { + "inserted_at": "2024-08-14T10:00:00.000000Z", + "updated_at": "2024-08-14T10:00:00.000000Z", + "provider": "aws", + "provider_id": "001", + "alias": "Amazon EC2", + "regions": ["us-east-1", "us-east-2"], + "findings": { + "failed": 3, + "last_checked_at": null + }, + "scanner_args": {} + } + }, + { + "type": "Service", + "id": "002", + "attributes": { + "inserted_at": "2024-08-14T10:01:00.000000Z", + "updated_at": "2024-08-14T10:01:00.000000Z", + "provider": "aws", + "provider_id": "002", + "alias": "Amazon EMR", + "regions": ["us-west-1", "us-west-2"], + "findings": { + "failed": 0, + "last_checked_at": null + }, + "scanner_args": {} + } + }, + { + "type": "Service", + "id": "003", + "attributes": { + "inserted_at": "2024-08-14T10:02:00.000000Z", + "updated_at": "2024-08-14T10:02:00.000000Z", + "provider": "aws", + "provider_id": "003", + "alias": "Amazon GuardDuty", + "regions": ["eu-west-1", "eu-west-2"], + "findings": { + "failed": 5, + "last_checked_at": null + }, + "scanner_args": {} + } + }, + { + "type": "Service", + "id": "004", + "attributes": { + "inserted_at": "2024-08-14T10:03:00.000000Z", + "updated_at": "2024-08-14T10:03:00.000000Z", + "provider": "aws", + "provider_id": "004", + "alias": "Amazon Inspector", + "regions": ["ap-southeast-1", "ap-southeast-2"], + "findings": { + "failed": 0, + "last_checked_at": null + }, + "scanner_args": {} + } + }, + { + "type": "Service", + "id": "005", + "attributes": { + "inserted_at": "2024-08-14T10:04:00.000000Z", + "updated_at": "2024-08-14T10:04:00.000000Z", + "provider": "aws", + "provider_id": "005", + "alias": "Amazon Macie", + "regions": ["eu-north-1", "eu-north-2"], + "findings": { + "failed": 2, + "last_checked_at": null + }, + "scanner_args": {} + } + }, + { + "type": "Service", + "id": "006", + "attributes": { + "inserted_at": "2024-08-14T10:05:00.000000Z", + "updated_at": "2024-08-14T10:05:00.000000Z", + "provider": "aws", + "provider_id": "006", + "alias": "Amazon RDS", + "regions": ["sa-east-1", "sa-east-2"], + "findings": { + "failed": 0, + "last_checked_at": null + }, + "scanner_args": {} + } + }, + { + "type": "Service", + "id": "007", + "attributes": { + "inserted_at": "2024-08-14T10:06:00.000000Z", + "updated_at": "2024-08-14T10:06:00.000000Z", + "provider": "aws", + "provider_id": "007", + "alias": "Amazon Route 53", + "regions": ["af-south-1", "af-south-2"], + "findings": { + "failed": 1, + "last_checked_at": null + }, + "scanner_args": {} + } + }, + { + "type": "Service", + "id": "008", + "attributes": { + "inserted_at": "2024-08-14T10:07:00.000000Z", + "updated_at": "2024-08-14T10:07:00.000000Z", + "provider": "aws", + "provider_id": "008", + "alias": "Amazon S3", + "regions": ["us-east-1", "us-west-1"], + "findings": { + "failed": 3, + "last_checked_at": null + }, + "scanner_args": {} + } + }, + { + "type": "Service", + "id": "009", + "attributes": { + "inserted_at": "2024-08-14T10:08:00.000000Z", + "updated_at": "2024-08-14T10:08:00.000000Z", + "provider": "aws", + "provider_id": "009", + "alias": "Amazon SNS", + "regions": ["eu-central-1", "eu-central-2"], + "findings": { + "failed": 0, + "last_checked_at": null + }, + "scanner_args": {} + } + }, + { + "type": "Service", + "id": "010", + "attributes": { + "inserted_at": "2024-08-14T10:09:00.000000Z", + "updated_at": "2024-08-14T10:09:00.000000Z", + "provider": "aws", + "provider_id": "010", + "alias": "Amazon VPC", + "regions": ["us-west-1", "us-west-2"], + "findings": { + "failed": 4, + "last_checked_at": null + }, + "scanner_args": {} + } + }, + { + "type": "Service", + "id": "011", + "attributes": { + "inserted_at": "2024-08-14T10:10:00.000000Z", + "updated_at": "2024-08-14T10:10:00.000000Z", + "provider": "aws", + "provider_id": "011", + "alias": "AWS Account", + "regions": ["eu-west-1", "eu-west-3"], + "findings": { + "failed": 0, + "last_checked_at": null + }, + "scanner_args": {} + } + }, + { + "type": "Service", + "id": "012", + "attributes": { + "inserted_at": "2024-08-14T10:11:00.000000Z", + "updated_at": "2024-08-14T10:11:00.000000Z", + "provider": "aws", + "provider_id": "012", + "alias": "AWS Athena", + "regions": ["us-east-1", "us-east-2"], + "findings": { + "failed": 5, + "last_checked_at": null + }, + "scanner_args": {} + } + }, + { + "type": "Service", + "id": "013", + "attributes": { + "inserted_at": "2024-08-14T10:12:00.000000Z", + "updated_at": "2024-08-14T10:12:00.000000Z", + "provider": "aws", + "provider_id": "013", + "alias": "AWS Certificate Manager", + "regions": ["us-west-1", "us-west-2"], + "findings": { + "failed": 1, + "last_checked_at": null + }, + "scanner_args": {} + } + }, + { + "type": "Service", + "id": "014", + "attributes": { + "inserted_at": "2024-08-14T10:13:00.000000Z", + "updated_at": "2024-08-14T10:13:00.000000Z", + "provider": "aws", + "provider_id": "014", + "alias": "AWS CloudFormation", + "regions": ["eu-central-1", "eu-central-2"], + "findings": { + "failed": 0, + "last_checked_at": null + }, + "scanner_args": {} + } + }, + { + "type": "Service", + "id": "015", + "attributes": { + "inserted_at": "2024-08-14T10:14:00.000000Z", + "updated_at": "2024-08-14T10:14:00.000000Z", + "provider": "aws", + "provider_id": "015", + "alias": "AWS CloudTrail", + "regions": ["us-east-1", "us-east-2"], + "findings": { + "failed": 4, + "last_checked_at": null + }, + "scanner_args": {} + } + }, + { + "type": "Service", + "id": "016", + "attributes": { + "inserted_at": "2024-08-14T10:15:00.000000Z", + "updated_at": "2024-08-14T10:15:00.000000Z", + "provider": "aws", + "provider_id": "016", + "alias": "AWS CloudWatch", + "regions": ["us-west-1", "us-west-2"], + "findings": { + "failed": 2, + "last_checked_at": null + }, + "scanner_args": {} + } + }, + { + "type": "Service", + "id": "017", + "attributes": { + "inserted_at": "2024-08-14T10:16:00.000000Z", + "updated_at": "2024-08-14T10:16:00.000000Z", + "provider": "aws", + "provider_id": "017", + "alias": "AWS Config", + "regions": ["eu-west-1", "eu-west-3"], + "findings": { + "failed": 0, + "last_checked_at": null + }, + "scanner_args": {} + } + }, + { + "type": "Service", + "id": "018", + "attributes": { + "inserted_at": "2024-08-14T10:17:00.000000Z", + "updated_at": "2024-08-14T10:17:00.000000Z", + "provider": "aws", + "provider_id": "018", + "alias": "AWS Database Migration Service", + "regions": ["us-east-1", "us-east-2"], + "findings": { + "failed": 5, + "last_checked_at": null + }, + "scanner_args": {} + } + }, + { + "type": "Service", + "id": "019", + "attributes": { + "inserted_at": "2024-08-14T10:18:00.000000Z", + "updated_at": "2024-08-14T10:18:00.000000Z", + "provider": "aws", + "provider_id": "019", + "alias": "AWS Glue", + "regions": ["us-west-1", "us-west-2"], + "findings": { + "failed": 1, + "last_checked_at": null + }, + "scanner_args": {} + } + }, + { + "type": "Service", + "id": "020", + "attributes": { + "inserted_at": "2024-08-14T10:19:00.000000Z", + "updated_at": "2024-08-14T10:19:00.000000Z", + "provider": "aws", + "provider_id": "020", + "alias": "AWS IAM", + "regions": ["eu-central-1", "eu-central-2"], + "findings": { + "failed": 0, + "last_checked_at": null + }, + "scanner_args": {} + } + }, + { + "type": "Service", + "id": "021", + "attributes": { + "inserted_at": "2024-08-14T10:20:00.000000Z", + "updated_at": "2024-08-14T10:20:00.000000Z", + "provider": "aws", + "provider_id": "021", + "alias": "AWS Lambda", + "regions": ["us-east-1", "us-east-2"], + "findings": { + "failed": 4, + "last_checked_at": null + }, + "scanner_args": {} + } + }, + { + "type": "Service", + "id": "022", + "attributes": { + "inserted_at": "2024-08-14T10:21:00.000000Z", + "updated_at": "2024-08-14T10:21:00.000000Z", + "provider": "aws", + "provider_id": "022", + "alias": "AWS Network Firewall", + "regions": ["us-west-1", "us-west-2"], + "findings": { + "failed": 2, + "last_checked_at": null + }, + "scanner_args": {} + } + }, + { + "type": "Service", + "id": "023", + "attributes": { + "inserted_at": "2024-08-14T10:22:00.000000Z", + "updated_at": "2024-08-14T10:22:00.000000Z", + "provider": "aws", + "provider_id": "023", + "alias": "AWS Organizations", + "regions": ["eu-west-1", "eu-west-3"], + "findings": { + "failed": 0, + "last_checked_at": null + }, + "scanner_args": {} + } + }, + { + "type": "Service", + "id": "024", + "attributes": { + "inserted_at": "2024-08-14T10:23:00.000000Z", + "updated_at": "2024-08-14T10:23:00.000000Z", + "provider": "aws", + "provider_id": "024", + "alias": "AWS Resource Explorer", + "regions": ["us-east-1", "us-east-2"], + "findings": { + "failed": 3, + "last_checked_at": null + }, + "scanner_args": {} + } + }, + { + "type": "Service", + "id": "025", + "attributes": { + "inserted_at": "2024-08-14T10:24:00.000000Z", + "updated_at": "2024-08-14T10:24:00.000000Z", + "provider": "aws", + "provider_id": "025", + "alias": "AWS Security Hub", + "regions": ["us-west-1", "us-west-2"], + "findings": { + "failed": 5, + "last_checked_at": null + }, + "scanner_args": {} + } + }, + { + "type": "Service", + "id": "026", + "attributes": { + "inserted_at": "2024-08-14T10:25:00.000000Z", + "updated_at": "2024-08-14T10:25:00.000000Z", + "provider": "aws", + "provider_id": "026", + "alias": "AWS Systems Manager Incident Manager", + "regions": ["eu-west-1", "eu-west-3"], + "findings": { + "failed": 0, + "last_checked_at": null + }, + "scanner_args": {} + } + }, + { + "type": "Service", + "id": "027", + "attributes": { + "inserted_at": "2024-08-14T10:26:00.000000Z", + "updated_at": "2024-08-14T10:26:00.000000Z", + "provider": "aws", + "provider_id": "027", + "alias": "AWS Trusted Advisor", + "regions": ["us-east-1", "us-east-2"], + "findings": { + "failed": 2, + "last_checked_at": null + }, + "scanner_args": {} + } + }, + { + "type": "Service", + "id": "028", + "attributes": { + "inserted_at": "2024-08-14T10:27:00.000000Z", + "updated_at": "2024-08-14T10:27:00.000000Z", + "provider": "aws", + "provider_id": "028", + "alias": "IAM Access Analyzer", + "regions": ["eu-west-1", "eu-west-3"], + "findings": { + "failed": 0, + "last_checked_at": null + }, + "scanner_args": {} + } + } + ], + "meta": { + "pagination": { + "page": 1, + "pages": 1, + "count": 28 + }, + "version": "v1" + } +} From 11518a080624af0bc2d1fbe296da9697d8dfbc3e Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Wed, 14 Aug 2024 16:01:56 +0200 Subject: [PATCH 121/411] chore: style tweaks --- components/filters/CustomDatePicker.tsx | 4 ++-- components/filters/FilterControls.tsx | 2 +- components/icons/services/IconServices.tsx | 4 ++-- components/services/CardService.tsx | 20 +++++++++++--------- dataServices.json | 4 ++-- 5 files changed, 18 insertions(+), 16 deletions(-) diff --git a/components/filters/CustomDatePicker.tsx b/components/filters/CustomDatePicker.tsx index d9657b9892..667078bc8f 100644 --- a/components/filters/CustomDatePicker.tsx +++ b/components/filters/CustomDatePicker.tsx @@ -23,7 +23,7 @@ export const CustomDatePicker = () => { const nextMonth = startOfMonth(now.add({ months: 1 })); return ( -
+
} CalendarTopContent={ @@ -55,7 +55,7 @@ export const CustomDatePicker = () => { size="sm" variant="flat" /> -

+

Selected date:{" "} {value ? formatter.format(value.toDate(getLocalTimeZone())) : "--"}

diff --git a/components/filters/FilterControls.tsx b/components/filters/FilterControls.tsx index a3ed124159..cd9b5654bc 100644 --- a/components/filters/FilterControls.tsx +++ b/components/filters/FilterControls.tsx @@ -4,7 +4,7 @@ import { CustomSelectProvider } from "./CustomSelectProvider"; export const FilterControls = () => { return ( -
+
diff --git a/components/icons/services/IconServices.tsx b/components/icons/services/IconServices.tsx index 2fd943604e..2678b3ef7a 100644 --- a/components/icons/services/IconServices.tsx +++ b/components/icons/services/IconServices.tsx @@ -895,7 +895,7 @@ export const getAWSIcon = (serviceAlias: string) => { return ; case "AWS Config": return ; - case "AWS Database Migration Service": + case "AWS Database Migration": return ; case "AWS Glue": return ; @@ -911,7 +911,7 @@ export const getAWSIcon = (serviceAlias: string) => { return ; case "AWS Security Hub": return ; - case "AWS Systems Manager Incident Manager": + case "AWS Systems Manager": return ; case "AWS Trusted Advisor": return ; diff --git a/components/services/CardService.tsx b/components/services/CardService.tsx index eaae8dc450..6216217607 100644 --- a/components/services/CardService.tsx +++ b/components/services/CardService.tsx @@ -12,15 +12,17 @@ export const CardService: React.FC = ({ }) => { return ( - - {getAWSIcon(serviceAlias)} -
-

{serviceAlias}

- - {fidingsFailed > 0 - ? `${fidingsFailed} Failed Findings` - : "All findings passed"} - + +
+ {getAWSIcon(serviceAlias)} +
+

{serviceAlias}

+ + {fidingsFailed > 0 + ? `${fidingsFailed} Failed Findings` + : "All findings passed"} + +
Date: Wed, 14 Aug 2024 16:29:52 +0200 Subject: [PATCH 122/411] chore: add input for muted findings --- components/filters/CustomCheckboxMutedFindings.tsx | 10 ++++++++++ components/filters/FilterControls.tsx | 4 +++- components/filters/index.ts | 1 + 3 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 components/filters/CustomCheckboxMutedFindings.tsx diff --git a/components/filters/CustomCheckboxMutedFindings.tsx b/components/filters/CustomCheckboxMutedFindings.tsx new file mode 100644 index 0000000000..ce5806d45d --- /dev/null +++ b/components/filters/CustomCheckboxMutedFindings.tsx @@ -0,0 +1,10 @@ +import { Checkbox } from "@nextui-org/react"; +import React from "react"; + +export const CustomCheckboxMutedFindings = () => { + return ( + + Include Muted Findings + + ); +}; diff --git a/components/filters/FilterControls.tsx b/components/filters/FilterControls.tsx index cd9b5654bc..010a834f63 100644 --- a/components/filters/FilterControls.tsx +++ b/components/filters/FilterControls.tsx @@ -1,13 +1,15 @@ import { CustomAccountSelection } from "./CustomAccountSelection"; +import { CustomCheckboxMutedFindings } from "./CustomCheckboxMutedFindings"; import { CustomDatePicker } from "./CustomDatePicker"; import { CustomSelectProvider } from "./CustomSelectProvider"; export const FilterControls = () => { return ( -
+
+
); }; diff --git a/components/filters/index.ts b/components/filters/index.ts index ba1a6814a1..0699cbd36a 100644 --- a/components/filters/index.ts +++ b/components/filters/index.ts @@ -1,4 +1,5 @@ export * from "../filters/CustomAccountSelection"; +export * from "../filters/CustomCheckboxMutedFindings"; export * from "../filters/CustomDatePicker"; export * from "../filters/CustomProviderInputs"; export * from "../filters/CustomSelectProvider"; From 088b4fa4fe0bbc743db0db77e2abc32f096839ee Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Thu, 15 Aug 2024 08:16:15 +0200 Subject: [PATCH 123/411] rename: rename ServiceCard component --- components/services/{CardService.tsx => ServiceCard.tsx} | 6 ++++-- components/services/index.ts | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) rename components/services/{CardService.tsx => ServiceCard.tsx} (87%) diff --git a/components/services/CardService.tsx b/components/services/ServiceCard.tsx similarity index 87% rename from components/services/CardService.tsx rename to components/services/ServiceCard.tsx index 6216217607..ab1cd020dc 100644 --- a/components/services/CardService.tsx +++ b/components/services/ServiceCard.tsx @@ -6,7 +6,7 @@ interface CardServiceProps { fidingsFailed: number; serviceAlias: string; } -export const CardService: React.FC = ({ +export const ServiceCard: React.FC = ({ fidingsFailed, serviceAlias, }) => { @@ -16,7 +16,9 @@ export const CardService: React.FC = ({
{getAWSIcon(serviceAlias)}
-

{serviceAlias}

+

+ {serviceAlias} +

{fidingsFailed > 0 ? `${fidingsFailed} Failed Findings` diff --git a/components/services/index.ts b/components/services/index.ts index ea59ab0d9b..57d2b66d6a 100644 --- a/components/services/index.ts +++ b/components/services/index.ts @@ -1 +1 @@ -export * from "./CardService"; +export * from "./ServiceCard"; From d7fe3595d3c7236b3cb90f7125128d720fbe3585 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Thu, 15 Aug 2024 08:16:52 +0200 Subject: [PATCH 124/411] chore: Add breakpoint to optimize layouts for large screens --- tailwind.config.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tailwind.config.js b/tailwind.config.js index ac42eb57ec..333f9f5d0f 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -36,6 +36,9 @@ module.exports = { "accordion-down": "accordion-down 0.2s ease-out", "accordion-up": "accordion-up 0.2s ease-out", }, + screens: { + "3xl": "1920px", // Add breakpoint to optimize layouts for large screens. + }, }, }, plugins: [require("tailwindcss-animate"), nextui()], From 614548f58a12dd5a4f689d86d1b28c88c0611b1b Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Thu, 15 Aug 2024 08:29:03 +0200 Subject: [PATCH 125/411] chore: adjust breakpoints for improved responsiveness --- app/(prowler)/services/page.tsx | 6 +++--- components/filters/CustomCheckboxMutedFindings.tsx | 2 +- components/filters/FilterControls.tsx | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/(prowler)/services/page.tsx b/app/(prowler)/services/page.tsx index e9af97846a..9f955a2921 100644 --- a/app/(prowler)/services/page.tsx +++ b/app/(prowler)/services/page.tsx @@ -5,7 +5,7 @@ import { Suspense } from "react"; import { getService } from "@/actions/services"; import { FilterControls } from "@/components/filters"; import { SkeletonTableProvider } from "@/components/providers"; -import { CardService } from "@/components/services"; +import { ServiceCard } from "@/components/services"; import { Header } from "@/components/ui"; import { searchParamsProps } from "@/types"; @@ -34,9 +34,9 @@ const SSRServiceGrid = async ({ searchParams }: searchParamsProps) => { if (services?.errors) redirect("/services"); return ( -
+
{services.services?.data.map((service: any) => ( - { return ( - + Include Muted Findings ); diff --git a/components/filters/FilterControls.tsx b/components/filters/FilterControls.tsx index 010a834f63..5ec7fe418a 100644 --- a/components/filters/FilterControls.tsx +++ b/components/filters/FilterControls.tsx @@ -5,7 +5,7 @@ import { CustomSelectProvider } from "./CustomSelectProvider"; export const FilterControls = () => { return ( -
+
From 5af439d9261021dcdd53a39c9d222ebbbd10a955 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Thu, 15 Aug 2024 09:09:43 +0200 Subject: [PATCH 126/411] feat: add Skeleton for services page --- app/(prowler)/services/page.tsx | 5 ++--- components/services/ServiceSkeletonGrid.tsx | 18 ++++++++++++++++++ components/services/index.ts | 1 + 3 files changed, 21 insertions(+), 3 deletions(-) create mode 100644 components/services/ServiceSkeletonGrid.tsx diff --git a/app/(prowler)/services/page.tsx b/app/(prowler)/services/page.tsx index 9f955a2921..ea675b553f 100644 --- a/app/(prowler)/services/page.tsx +++ b/app/(prowler)/services/page.tsx @@ -4,8 +4,7 @@ import { Suspense } from "react"; import { getService } from "@/actions/services"; import { FilterControls } from "@/components/filters"; -import { SkeletonTableProvider } from "@/components/providers"; -import { ServiceCard } from "@/components/services"; +import { ServiceCard, ServiceSkeletonGrid } from "@/components/services"; import { Header } from "@/components/ui"; import { searchParamsProps } from "@/types"; @@ -19,7 +18,7 @@ export default async function Services({ searchParams }: searchParamsProps) { - }> + }> diff --git a/components/services/ServiceSkeletonGrid.tsx b/components/services/ServiceSkeletonGrid.tsx new file mode 100644 index 0000000000..9ea35c1385 --- /dev/null +++ b/components/services/ServiceSkeletonGrid.tsx @@ -0,0 +1,18 @@ +import { Card, Skeleton } from "@nextui-org/react"; +import React from "react"; + +export const ServiceSkeletonGrid = () => { + return ( + +
+ {[...Array(25)].map((_, index) => ( +
+ +
+
+
+ ))} +
+
+ ); +}; diff --git a/components/services/index.ts b/components/services/index.ts index 57d2b66d6a..d75bdbfb98 100644 --- a/components/services/index.ts +++ b/components/services/index.ts @@ -1 +1,2 @@ export * from "./ServiceCard"; +export * from "./ServiceSkeletonGrid"; From 6ea3057b236f9bbdb35f601494d1bdb29d9b2961 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Fri, 16 Aug 2024 10:59:09 +0200 Subject: [PATCH 127/411] feat: create CustomBox component --- components/ui/custom/CustomBox.tsx | 34 ++++++++++++++++++++++++++++++ components/ui/custom/index.ts | 1 + 2 files changed, 35 insertions(+) create mode 100644 components/ui/custom/CustomBox.tsx create mode 100644 components/ui/custom/index.ts diff --git a/components/ui/custom/CustomBox.tsx b/components/ui/custom/CustomBox.tsx new file mode 100644 index 0000000000..acc4b3d1c8 --- /dev/null +++ b/components/ui/custom/CustomBox.tsx @@ -0,0 +1,34 @@ +import { Card, CardBody, CardHeader, Divider } from "@nextui-org/react"; +import React from "react"; + +interface CustomBoxProps { + children: React.ReactNode; + preTitle?: string; + subTitle?: string; + title?: string; +} + +export const CustomBox = ({ + children, + preTitle, + subTitle, + title, +}: CustomBoxProps) => { + return ( + + {(preTitle || subTitle || title) && ( + <> + + {preTitle && ( +

{preTitle}

+ )} + {subTitle && {subTitle}} + {title &&

{title}

} +
+ + + )} + {children} +
+ ); +}; diff --git a/components/ui/custom/index.ts b/components/ui/custom/index.ts new file mode 100644 index 0000000000..00732c48c8 --- /dev/null +++ b/components/ui/custom/index.ts @@ -0,0 +1 @@ +export * from "./CustomBox"; From 75f4f0d43ac1ef29552a5d29bc6d72f000c94a15 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Fri, 16 Aug 2024 11:16:42 +0200 Subject: [PATCH 128/411] chore: update tailwind-merge dependencie --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2729d64a84..bdcbb7fa0a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,7 +32,7 @@ "react-dom": "18.3.1", "server-only": "^0.0.1", "shadcn-ui": "^0.2.3", - "tailwind-merge": "^2.4.0", + "tailwind-merge": "^2.5.2", "tailwindcss-animate": "^1.0.7" }, "devDependencies": { @@ -10205,9 +10205,9 @@ } }, "node_modules/tailwind-merge": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.4.0.tgz", - "integrity": "sha512-49AwoOQNKdqKPd9CViyH5wJoSKsCDjUlzL8DxuGp3P1FsGY36NJDAa18jLZcaHAUUuTj+JB8IAo8zWgBNvBF7A==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.5.2.tgz", + "integrity": "sha512-kjEBm+pvD+6eAwzJL2Bi+02/9LFLal1Gs61+QB7HvTfQQ0aXwC5LGT8PEt1gS0CWKktKe6ysPTAy3cBC5MeiIg==", "funding": { "type": "github", "url": "https://github.com/sponsors/dcastil" diff --git a/package.json b/package.json index 8bda832bff..522d304b15 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "react-dom": "18.3.1", "server-only": "^0.0.1", "shadcn-ui": "^0.2.3", - "tailwind-merge": "^2.4.0", + "tailwind-merge": "^2.5.2", "tailwindcss-animate": "^1.0.7" }, "devDependencies": { From caa5e7dd96586785df56744966a8aab858a2305e Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Fri, 16 Aug 2024 11:21:29 +0200 Subject: [PATCH 129/411] chore: add recharts library --- package-lock.json | 297 +++++++++++++++++++++++++++++++++++++++++++++- package.json | 1 + 2 files changed, 293 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index bdcbb7fa0a..a836c8c7ad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,6 +30,7 @@ "next-themes": "^0.2.1", "react": "18.3.1", "react-dom": "18.3.1", + "recharts": "^2.12.7", "server-only": "^0.0.1", "shadcn-ui": "^0.2.3", "tailwind-merge": "^2.5.2", @@ -4081,6 +4082,60 @@ "url": "https://github.com/sponsors/isaacs" } }, + "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/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", @@ -5201,8 +5256,117 @@ "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "devOptional": true + "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/damerau-levenshtein": { "version": "1.0.8", @@ -5294,6 +5458,11 @@ } } }, + "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-equal": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz", @@ -5432,6 +5601,15 @@ "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/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -6531,6 +6709,14 @@ "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", + "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", @@ -7176,6 +7362,14 @@ "node": ">= 0.4" } }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "engines": { + "node": ">=12" + } + }, "node_modules/intl-messageformat": { "version": "10.5.14", "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.5.14.tgz", @@ -7971,6 +8165,11 @@ "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.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -9098,7 +9297,6 @@ "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dev": true, "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", @@ -9159,8 +9357,7 @@ "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==", - "dev": true + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, "node_modules/react-remove-scroll": { "version": "2.5.10", @@ -9207,6 +9404,20 @@ } } }, + "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", @@ -9245,6 +9456,21 @@ "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/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -9277,6 +9503,41 @@ "node": ">=8.10.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/recharts/node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + }, "node_modules/reflect.getprototypeof": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz", @@ -10315,6 +10576,11 @@ "node": ">=0.8" } }, + "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", @@ -10639,6 +10905,27 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, + "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/wcwidth": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", diff --git a/package.json b/package.json index 522d304b15..0b4e61a895 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "next-themes": "^0.2.1", "react": "18.3.1", "react-dom": "18.3.1", + "recharts": "^2.12.7", "server-only": "^0.0.1", "shadcn-ui": "^0.2.3", "tailwind-merge": "^2.5.2", From b0ec7a2a82909dd502ccbce227bea4f74740f33c Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Fri, 16 Aug 2024 12:43:02 +0200 Subject: [PATCH 130/411] fix: temporary rename to resolve casing conflict --- .eslintrc.cjs | 1 + app/(prowler)/page.tsx | 30 ++- components/charts/StatusChart.tsx | 107 +++++++++ components/charts/index.ts | 1 + components/ui/chart/tempChart.tsx | 370 +++++++++++++++++++++++++++++ components/ui/custom/CustomBox.tsx | 7 +- components/ui/index.ts | 1 + styles/globals.css | 19 ++ 8 files changed, 531 insertions(+), 5 deletions(-) create mode 100644 components/charts/StatusChart.tsx create mode 100644 components/charts/index.ts create mode 100644 components/ui/chart/tempChart.tsx diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 230bd6691f..c9f6d992bf 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -37,5 +37,6 @@ module.exports = { "simple-import-sort/exports": "error", "jsx-a11y/anchor-is-valid": "error", "jsx-a11y/alt-text": "error", + "@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }], }, }; diff --git a/app/(prowler)/page.tsx b/app/(prowler)/page.tsx index 3882fd7071..7cf4e2273f 100644 --- a/app/(prowler)/page.tsx +++ b/app/(prowler)/page.tsx @@ -1,11 +1,37 @@ +import { Spacer } from "@nextui-org/react"; + +import { StatusChart } from "@/components/charts"; +import { FilterControls } from "@/components/filters"; import { Header } from "@/components/ui"; +import { CustomBox } from "@/components/ui/custom"; export default function Home() { return ( <>
- -

Hi hi from Overview page

+ + + +
+ + + + +

hi hi

+
+ +

hi hi

+
+
); } diff --git a/components/charts/StatusChart.tsx b/components/charts/StatusChart.tsx new file mode 100644 index 0000000000..7f68ae2842 --- /dev/null +++ b/components/charts/StatusChart.tsx @@ -0,0 +1,107 @@ +"use client"; + +import { TrendingUp } from "lucide-react"; +import * as React from "react"; +import { Label, Pie, PieChart } from "recharts"; + +import { + ChartConfig, + ChartContainer, + ChartTooltip, + ChartTooltipContent, +} from "../ui"; + +const chartData = [ + { findings: "Success", number: 436, fill: "var(--color-success)" }, + { findings: "Fail", number: 293, fill: "var(--color-fail)" }, +]; + +const chartConfig = { + number: { + label: "Findings", + }, + chrome: { + label: "Chrome", + color: "hsl(var(--chart-1))", + }, + success: { + label: "Success", + color: "hsl(var(--chart-success))", + }, + firefox: { + label: "Firefox", + color: "hsl(var(--chart-3))", + }, + edge: { + label: "Edge", + color: "hsl(var(--chart-4))", + }, + fail: { + label: "Fail", + color: "hsl(var(--chart-fail))", + }, +} satisfies ChartConfig; + +export function StatusChart() { + const totalVisitors = React.useMemo(() => { + return chartData.reduce((acc, curr) => acc + curr.number, 0); + }, []); + + return ( +
+ + + } /> + + + + +
+
+ No change from last scan +
+
+ +2 findings from last scan +
+
+
+ ); +} diff --git a/components/charts/index.ts b/components/charts/index.ts new file mode 100644 index 0000000000..6883b502b9 --- /dev/null +++ b/components/charts/index.ts @@ -0,0 +1 @@ +export * from "./StatusChart"; diff --git a/components/ui/chart/tempChart.tsx b/components/ui/chart/tempChart.tsx new file mode 100644 index 0000000000..a83d876ec4 --- /dev/null +++ b/components/ui/chart/tempChart.tsx @@ -0,0 +1,370 @@ +"use client"; + +import * as React from "react"; +import * as RechartsPrimitive from "recharts"; + +// import { +// NameType, +// Payload, +// ValueType, +// } from "recharts/types/component/DefaultTooltipContent"; +import { cn } from "@/lib/utils"; + +// Format: { THEME_NAME: CSS_SELECTOR } +const THEMES = { light: "", dark: ".dark" } as const; + +export type ChartConfig = { + [k in string]: { + label?: React.ReactNode; + icon?: React.ComponentType; + } & ( + | { color?: string; theme?: never } + | { color?: never; theme: Record } + ); +}; + +type ChartContextProps = { + config: ChartConfig; +}; + +const ChartContext = React.createContext(null); + +function useChart() { + const context = React.useContext(ChartContext); + + if (!context) { + throw new Error("useChart must be used within a "); + } + + return context; +} + +const ChartContainer = React.forwardRef< + HTMLDivElement, + React.ComponentProps<"div"> & { + config: ChartConfig; + children: React.ComponentProps< + typeof RechartsPrimitive.ResponsiveContainer + >["children"]; + } +>(({ id, className, children, config, ...props }, ref) => { + const uniqueId = React.useId(); + const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`; + + return ( + +
+ + + {children} + +
+
+ ); +}); +ChartContainer.displayName = "Chart"; + +const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => { + const colorConfig = Object.entries(config).filter( + ([_, config]) => config.theme || config.color, + ); + + if (!colorConfig.length) { + return null; + } + + return ( + + + + + + + + + diff --git a/components/icons/compliance/cis.svg b/components/icons/compliance/cis.svg new file mode 100644 index 0000000000..013f35a75e --- /dev/null +++ b/components/icons/compliance/cis.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/components/icons/compliance/cisa.svg b/components/icons/compliance/cisa.svg new file mode 100644 index 0000000000..c26f681d51 --- /dev/null +++ b/components/icons/compliance/cisa.svg @@ -0,0 +1,2 @@ + + diff --git a/components/icons/compliance/ens.png b/components/icons/compliance/ens.png new file mode 100644 index 0000000000000000000000000000000000000000..c3e6433f31c087c14a09e9a4cbf06eb21578d332 GIT binary patch literal 97549 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¥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

w80jRaG@VNRu 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 z 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 Q0sP2HDN5!)F?{!b0IN+lq5uE@ literal 0 HcmV?d00001 diff --git a/components/icons/compliance/fedramp.svg b/components/icons/compliance/fedramp.svg new file mode 100644 index 0000000000..7706bd4eff --- /dev/null +++ b/components/icons/compliance/fedramp.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/components/icons/compliance/ffiec.svg b/components/icons/compliance/ffiec.svg new file mode 100644 index 0000000000..40951851e9 --- /dev/null +++ b/components/icons/compliance/ffiec.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/components/icons/compliance/gdpr.svg b/components/icons/compliance/gdpr.svg new file mode 100644 index 0000000000..31175e9afe --- /dev/null +++ b/components/icons/compliance/gdpr.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/components/icons/compliance/gxp-aws.svg b/components/icons/compliance/gxp-aws.svg new file mode 100644 index 0000000000..7b22b53de3 --- /dev/null +++ b/components/icons/compliance/gxp-aws.svg @@ -0,0 +1,455 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/components/icons/compliance/hipaa.svg b/components/icons/compliance/hipaa.svg new file mode 100644 index 0000000000..b9537032f6 --- /dev/null +++ b/components/icons/compliance/hipaa.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/components/icons/compliance/iso-27001.svg b/components/icons/compliance/iso-27001.svg new file mode 100644 index 0000000000..109c19521d --- /dev/null +++ b/components/icons/compliance/iso-27001.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/components/icons/compliance/mitre-attack.svg b/components/icons/compliance/mitre-attack.svg new file mode 100644 index 0000000000..c546eedd83 --- /dev/null +++ b/components/icons/compliance/mitre-attack.svg @@ -0,0 +1,315 @@ + + + + + + + + + + + + + + + diff --git a/components/icons/compliance/nist.svg b/components/icons/compliance/nist.svg new file mode 100644 index 0000000000..4231017c94 --- /dev/null +++ b/components/icons/compliance/nist.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/components/icons/compliance/pci-dss.svg b/components/icons/compliance/pci-dss.svg new file mode 100644 index 0000000000..52f93be38b --- /dev/null +++ b/components/icons/compliance/pci-dss.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/components/icons/compliance/rbi.svg b/components/icons/compliance/rbi.svg new file mode 100644 index 0000000000..ce1d81f83d --- /dev/null +++ b/components/icons/compliance/rbi.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/components/icons/compliance/soc2.svg b/components/icons/compliance/soc2.svg new file mode 100644 index 0000000000..d2b8782541 --- /dev/null +++ b/components/icons/compliance/soc2.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/components/icons/index.ts b/components/icons/index.ts index c057522527..7912d466fd 100644 --- a/components/icons/index.ts +++ b/components/icons/index.ts @@ -1,3 +1,4 @@ +export * from "./compliance/IconCompliance"; export * from "./Icons"; export * from "./prowler/ProwlerIcons"; export * from "./services/IconServices"; diff --git a/dataCompliance.json b/dataCompliance.json new file mode 100644 index 0000000000..a1bdd62831 --- /dev/null +++ b/dataCompliance.json @@ -0,0 +1,438 @@ +{ + "links": { + "first": "http://localhost:8080/api/v1/compliance?page%5Bnumber%5D=1", + "last": "http://localhost:8080/api/v1/compliance?page%5Bnumber%5D=1", + "next": null, + "prev": null + }, + "data": [ + { + "id": "001", + "attributes": { + "title": "AWS Account Security Onboarding", + "passingRequirements": 28, + "totalRequirements": 83, + "regions": ["us-east-1", "us-east-2"] + }, + "lastScan": { + "attributes": { + "passingRequirements": 28, + "totalRequirements": 83 + } + } + }, + { + "id": "002", + "attributes": { + "title": "AWS Audit Manager Control Tower Guardrails", + "passingRequirements": 10, + "totalRequirements": 14, + "regions": ["us-west-1", "us-west-2"] + }, + "lastScan": { + "attributes": { + "passingRequirements": 14, + "totalRequirements": 14 + } + } + }, + { + "id": "003", + "attributes": { + "title": "AWS Foundational Security Best Practices", + "passingRequirements": 22, + "totalRequirements": 37, + "regions": ["eu-west-1", "eu-west-2"] + }, + "lastScan": { + "attributes": { + "passingRequirements": 22, + "totalRequirements": 37 + } + } + }, + { + "id": "004", + "attributes": { + "title": "AWS Foundational Technical Review", + "passingRequirements": 3, + "totalRequirements": 45, + "regions": ["ap-southeast-1", "ap-southeast-2"] + }, + "lastScan": { + "attributes": { + "passingRequirements": 25, + "totalRequirements": 45 + } + } + }, + { + "id": "005", + "attributes": { + "title": "AWS Well Architected Framework Reliability Pillar", + "passingRequirements": 2, + "totalRequirements": 3, + "regions": [] + }, + "lastScan": { + "attributes": { + "passingRequirements": 0, + "totalRequirements": 3 + } + } + }, + { + "id": "006", + "attributes": { + "title": "AWS Well Architected Framework Security Pillar", + "passingRequirements": 19, + "totalRequirements": 57, + "regions": [] + }, + "lastScan": { + "attributes": { + "passingRequirements": 24, + "totalRequirements": 57 + } + } + }, + { + "id": "007", + "attributes": { + "title": "CIS 1.4", + "passingRequirements": 43, + "totalRequirements": 58, + "regions": [] + }, + "lastScan": { + "attributes": { + "passingRequirements": 40, + "totalRequirements": 58 + } + } + }, + { + "id": "008", + "attributes": { + "title": "CIS 1.5", + "passingRequirements": 48, + "totalRequirements": 63, + "regions": [] + }, + "lastScan": { + "attributes": { + "passingRequirements": 50, + "totalRequirements": 63 + } + } + }, + { + "id": "009", + "attributes": { + "title": "CIS 2.0", + "passingRequirements": 47, + "totalRequirements": 64, + "regions": [] + }, + "lastScan": { + "attributes": { + "passingRequirements": 64, + "totalRequirements": 64 + } + } + }, + { + "id": "010", + "attributes": { + "title": "CIS 3.0", + "passingRequirements": 45, + "totalRequirements": 62, + "regions": [] + }, + "lastScan": { + "attributes": { + "passingRequirements": 48, + "totalRequirements": 62 + } + } + }, + { + "id": "011", + "attributes": { + "title": "CISA", + "passingRequirements": 4, + "totalRequirements": 16, + "regions": [] + }, + "lastScan": { + "attributes": { + "passingRequirements": 4, + "totalRequirements": 16 + } + } + }, + { + "id": "012", + "attributes": { + "title": "ENS RD2022 Categoría ALTA", + "passingRequirements": 89, + "totalRequirements": 189, + "regions": [] + }, + "lastScan": { + "attributes": { + "passingRequirements": 82, + "totalRequirements": 189 + } + } + }, + { + "id": "013", + "attributes": { + "title": "FFIEC", + "passingRequirements": 8, + "totalRequirements": 44, + "regions": [] + }, + "lastScan": { + "attributes": { + "passingRequirements": 40, + "totalRequirements": 44 + } + } + }, + { + "id": "014", + "attributes": { + "title": "FedRAMP Low Revision 4", + "passingRequirements": 4, + "totalRequirements": 18, + "regions": [] + }, + "lastScan": { + "attributes": { + "passingRequirements": 4, + "totalRequirements": 18 + } + } + }, + { + "id": "015", + "attributes": { + "title": "FedRamp Moderate Revision 4", + "passingRequirements": 11, + "totalRequirements": 64, + "regions": [] + }, + "lastScan": { + "attributes": { + "passingRequirements": 28, + "totalRequirements": 64 + } + } + }, + { + "id": "016", + "attributes": { + "title": "GDPR", + "passingRequirements": 0, + "totalRequirements": 3, + "regions": [] + }, + "lastScan": { + "attributes": { + "passingRequirements": 0, + "totalRequirements": 3 + } + } + }, + { + "id": "017", + "attributes": { + "title": "GxP 21 CFR Part 11", + "passingRequirements": 1, + "totalRequirements": 11, + "regions": [] + }, + "lastScan": { + "attributes": { + "passingRequirements": 11, + "totalRequirements": 11 + } + } + }, + { + "id": "018", + "attributes": { + "title": "GxP EU Annex 11", + "passingRequirements": 5, + "totalRequirements": 14, + "regions": [] + }, + "lastScan": { + "attributes": { + "passingRequirements": 5, + "totalRequirements": 14 + } + } + }, + { + "id": "019", + "attributes": { + "title": "HIPAA", + "passingRequirements": 2, + "totalRequirements": 32, + "regions": [] + }, + "lastScan": { + "attributes": { + "passingRequirements": 9, + "totalRequirements": 32 + } + } + }, + { + "id": "020", + "attributes": { + "title": "ISO27001 2013", + "passingRequirements": 55, + "totalRequirements": 79, + "regions": [] + }, + "lastScan": { + "attributes": { + "passingRequirements": 66, + "totalRequirements": 79 + } + } + }, + { + "id": "021", + "attributes": { + "title": "MITRE ATTACK", + "passingRequirements": 7, + "totalRequirements": 46, + "regions": [] + }, + "lastScan": { + "attributes": { + "passingRequirements": 19, + "totalRequirements": 46 + } + } + }, + { + "id": "022", + "attributes": { + "title": "NIST 800 171 Revision 2", + "passingRequirements": 3, + "totalRequirements": 50, + "regions": [] + }, + "lastScan": { + "attributes": { + "passingRequirements": 3, + "totalRequirements": 50 + } + } + }, + { + "id": "023", + "attributes": { + "title": "NIST 800 53 Revision 4", + "passingRequirements": 21, + "totalRequirements": 64, + "regions": [] + }, + "lastScan": { + "attributes": { + "passingRequirements": 21, + "totalRequirements": 64 + } + } + }, + { + "id": "024", + "attributes": { + "title": "NIST 800 53 Revision 5", + "passingRequirements": 75, + "totalRequirements": 288, + "regions": [] + }, + "lastScan": { + "attributes": { + "passingRequirements": 75, + "totalRequirements": 288 + } + } + }, + { + "id": "025", + "attributes": { + "title": "NIST CSF 1.1", + "passingRequirements": 15, + "totalRequirements": 56, + "regions": [] + }, + "lastScan": { + "attributes": { + "passingRequirements": 46, + "totalRequirements": 56 + } + } + }, + { + "id": "026", + "attributes": { + "title": "PCI 3.2.1", + "passingRequirements": 11, + "totalRequirements": 19, + "regions": [] + }, + "lastScan": { + "attributes": { + "passingRequirements": 19, + "totalRequirements": 19 + } + } + }, + { + "id": "027", + "attributes": { + "title": "RBI Cyber Security Framework", + "passingRequirements": 3, + "totalRequirements": 9, + "regions": [] + }, + "lastScan": { + "attributes": { + "passingRequirements": 3, + "totalRequirements": 9 + } + } + }, + { + "id": "028", + "attributes": { + "title": "SOC2", + "passingRequirements": 7, + "totalRequirements": 56, + "regions": [] + }, + "lastScan": { + "attributes": { + "passingRequirements": 7, + "totalRequirements": 56 + } + } + } + ], + "meta": { + "pagination": { + "page": 1, + "pages": 1, + "count": 28 + }, + "version": "v1" + } +} diff --git a/images.d.ts b/images.d.ts new file mode 100644 index 0000000000..ad238adbf8 --- /dev/null +++ b/images.d.ts @@ -0,0 +1,29 @@ +declare module "*.png" { + const value: string; + export default value; +} + +declare module "*.jpg" { + const value: string; + export default value; +} + +declare module "*.jpeg" { + const value: string; + export default value; +} + +declare module "*.gif" { + const value: string; + export default value; +} + +declare module "*.svg" { + const content: any; + export default content; +} + +declare module "*.webp" { + const value: string; + export default value; +} diff --git a/tsconfig.json b/tsconfig.json index f204766d1b..0a3c1cf1de 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -25,5 +25,11 @@ "target": "es5" }, "exclude": ["node_modules"], - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"] + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts", + "images.d.ts" + ] } From 80d05c276f5791b64b0af652bd9879afbdfd5caf Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Tue, 20 Aug 2024 16:23:02 +0200 Subject: [PATCH 145/411] chore: add basic routing for next auth --- .env.template | 2 + app/(auth)/layout.tsx | 16 +++++ app/(auth)/sign-in/page.tsx | 7 +++ app/(auth)/sign-up/page.tsx | 7 +++ auth.config.ts | 9 +++ package-lock.json | 119 ++++++++++++++++++++++++++++++++++++ package.json | 1 + 7 files changed, 161 insertions(+) create mode 100644 .env.template create mode 100644 app/(auth)/layout.tsx create mode 100644 app/(auth)/sign-in/page.tsx create mode 100644 app/(auth)/sign-up/page.tsx create mode 100644 auth.config.ts diff --git a/.env.template b/.env.template new file mode 100644 index 0000000000..35ac3b9b58 --- /dev/null +++ b/.env.template @@ -0,0 +1,2 @@ +# openssl rand -base64 32 +AUTH_SECRET=your-secret-key diff --git a/app/(auth)/layout.tsx b/app/(auth)/layout.tsx new file mode 100644 index 0000000000..5ff842ef07 --- /dev/null +++ b/app/(auth)/layout.tsx @@ -0,0 +1,16 @@ +export const metadata = { + title: "Next.js", + description: "Generated by Next.js", +}; + +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + {children} + + ); +} diff --git a/app/(auth)/sign-in/page.tsx b/app/(auth)/sign-in/page.tsx new file mode 100644 index 0000000000..e12af16f4c --- /dev/null +++ b/app/(auth)/sign-in/page.tsx @@ -0,0 +1,7 @@ +import React from "react"; + +const SignIn = () => { + return

SignIn
; +}; + +export default SignIn; diff --git a/app/(auth)/sign-up/page.tsx b/app/(auth)/sign-up/page.tsx new file mode 100644 index 0000000000..b466e40354 --- /dev/null +++ b/app/(auth)/sign-up/page.tsx @@ -0,0 +1,7 @@ +import React from "react"; + +const SignUp = () => { + return
SignUp
; +}; + +export default SignUp; diff --git a/auth.config.ts b/auth.config.ts new file mode 100644 index 0000000000..7baf25dd7d --- /dev/null +++ b/auth.config.ts @@ -0,0 +1,9 @@ +import type { NextAuthConfig } from "next-auth"; + +export const authConfig = { + pages: { + signIn: "/sign-in", + // signUp: "/sign-up", + }, + providers: [], +} satisfies NextAuthConfig; diff --git a/package-lock.json b/package-lock.json index ccef367e4e..0ce80ed402 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,6 +27,7 @@ "intl-messageformat": "^10.5.0", "lucide-react": "^0.417.0", "next": "14.2.3", + "next-auth": "^5.0.0-beta.20", "next-themes": "^0.2.1", "react": "18.3.1", "react-dom": "18.3.1", @@ -90,6 +91,36 @@ "nun": "bin/nun.mjs" } }, + "node_modules/@auth/core": { + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@auth/core/-/core-0.34.2.tgz", + "integrity": "sha512-KywHKRgLiF3l7PLyL73fjLSIBe1YNcA6sMeew4yMP6cfCWGXZrkkXd32AjRi1hlJ9nvovUBGZHvbn+LijO6ZeQ==", + "dependencies": { + "@panva/hkdf": "^1.1.1", + "@types/cookie": "0.6.0", + "cookie": "0.6.0", + "jose": "^5.1.3", + "oauth4webapi": "^2.10.4", + "preact": "10.11.3", + "preact-render-to-string": "5.2.3" + }, + "peerDependencies": { + "@simplewebauthn/browser": "^9.0.1", + "@simplewebauthn/server": "^9.0.2", + "nodemailer": "^6.8.0" + }, + "peerDependenciesMeta": { + "@simplewebauthn/browser": { + "optional": true + }, + "@simplewebauthn/server": { + "optional": true + }, + "nodemailer": { + "optional": true + } + } + }, "node_modules/@babel/code-frame": { "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", @@ -2233,6 +2264,14 @@ "node": ">= 8" } }, + "node_modules/@panva/hkdf": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.2.1.tgz", + "integrity": "sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -4082,6 +4121,11 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==" + }, "node_modules/@types/d3-array": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz", @@ -5204,6 +5248,14 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, + "node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/cosmiconfig": { "version": "8.3.6", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", @@ -7856,6 +7908,14 @@ "jiti": "bin/jiti.js" } }, + "node_modules/jose": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/jose/-/jose-5.7.0.tgz", + "integrity": "sha512-3P9qfTYDVnNn642LCAqIKbTGb9a1TBxZ9ti5zEVEr48aDdflgRjhspWFb6WM4PzAfFbGMJYC4+803v8riCRAKw==", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -8555,6 +8615,32 @@ } } }, + "node_modules/next-auth": { + "version": "5.0.0-beta.20", + "resolved": "https://registry.npmjs.org/next-auth/-/next-auth-5.0.0-beta.20.tgz", + "integrity": "sha512-+48SjV9k9AtUU3JbEIa4PXNjKIewfFjVGL7Xs2RKkuQ5QqegDNIQiIG8sLk6/qo7RTScQYIGKgeQ5IuQRtrTQg==", + "dependencies": { + "@auth/core": "0.34.2" + }, + "peerDependencies": { + "@simplewebauthn/browser": "^9.0.1", + "@simplewebauthn/server": "^9.0.2", + "next": "^14.0.0-0 || ^15.0.0-0", + "nodemailer": "^6.6.5", + "react": "^18.2.0 || ^19.0.0-0" + }, + "peerDependenciesMeta": { + "@simplewebauthn/browser": { + "optional": true + }, + "@simplewebauthn/server": { + "optional": true + }, + "nodemailer": { + "optional": true + } + } + }, "node_modules/next-themes": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.2.1.tgz", @@ -8684,6 +8770,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/oauth4webapi": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-2.12.0.tgz", + "integrity": "sha512-WFmcHzhFtq2Ar91crpGQZUD8DS0SG7Zti1AgbansUAfdpIsoRXE+hcMNi8MW6bGNNObWis0x8BZRl6K+FR4oQg==", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -9244,6 +9338,26 @@ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" }, + "node_modules/preact": { + "version": "10.11.3", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.11.3.tgz", + "integrity": "sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/preact-render-to-string": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-5.2.3.tgz", + "integrity": "sha512-aPDxUn5o3GhWdtJtW0svRC2SS/l8D9MAgo2+AWml+BhDImb27ALf04Q2d+AHqUUOc6RdSXFIBVa2gxzgMKgtZA==", + "dependencies": { + "pretty-format": "^3.8.0" + }, + "peerDependencies": { + "preact": ">=10" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -9281,6 +9395,11 @@ "node": ">=6.0.0" } }, + "node_modules/pretty-format": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz", + "integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==" + }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", diff --git a/package.json b/package.json index 2f7d6287ab..8f2d01c7c9 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "intl-messageformat": "^10.5.0", "lucide-react": "^0.417.0", "next": "14.2.3", + "next-auth": "^5.0.0-beta.20", "next-themes": "^0.2.1", "react": "18.3.1", "react-dom": "18.3.1", From 52dd08883f8ab04a1ca5e74f4fd0fe7cf418c641 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Wed, 21 Aug 2024 11:53:17 +0200 Subject: [PATCH 146/411] chore: add AuthForm component --- actions/auth.ts | 27 +++++++ actions/index.ts | 1 + app/(auth)/layout.tsx | 46 ++++++++++-- app/(auth)/sign-in/page.tsx | 4 +- app/(auth)/sign-up/page.tsx | 4 +- auth.config.ts | 25 ++++++- components/auth/AuthForm.tsx | 141 +++++++++++++++++++++++++++++++++++ components/auth/index.ts | 1 + package-lock.json | 3 +- package.json | 3 +- 10 files changed, 243 insertions(+), 12 deletions(-) create mode 100644 actions/auth.ts create mode 100644 components/auth/AuthForm.tsx create mode 100644 components/auth/index.ts diff --git a/actions/auth.ts b/actions/auth.ts new file mode 100644 index 0000000000..6523d2f8b4 --- /dev/null +++ b/actions/auth.ts @@ -0,0 +1,27 @@ +"use server"; + +import { AuthError } from "next-auth"; + +import { signIn } from "@/auth.config"; + +// ... + +export async function authenticate( + prevState: string | undefined, + formData: FormData, +) { + try { + console.log(formData); + await signIn("credentials", formData); + } catch (error) { + if (error instanceof AuthError) { + switch (error.type) { + case "CredentialsSignin": + return "Invalid credentials."; + default: + return "Something went wrong."; + } + } + throw error; + } +} diff --git a/actions/index.ts b/actions/index.ts index 5532383f5f..0085735bb3 100644 --- a/actions/index.ts +++ b/actions/index.ts @@ -1 +1,2 @@ +export * from "./auth"; export * from "./providers"; diff --git a/app/(auth)/layout.tsx b/app/(auth)/layout.tsx index 5ff842ef07..f1f003744e 100644 --- a/app/(auth)/layout.tsx +++ b/app/(auth)/layout.tsx @@ -1,6 +1,30 @@ -export const metadata = { - title: "Next.js", - description: "Generated by Next.js", +import "@/styles/globals.css"; + +import { Metadata, Viewport } from "next"; + +import { Toaster } from "@/components/ui"; +import { fontSans } from "@/config/fonts"; +import { siteConfig } from "@/config/site"; +import { cn } from "@/lib"; + +import { Providers } from "../providers"; + +export const metadata: Metadata = { + title: { + default: siteConfig.name, + template: `%s - ${siteConfig.name}`, + }, + description: siteConfig.description, + icons: { + icon: "/favicon.ico", + }, +}; + +export const viewport: Viewport = { + themeColor: [ + { media: "(prefers-color-scheme: light)", color: "white" }, + { media: "(prefers-color-scheme: dark)", color: "black" }, + ], }; export default function RootLayout({ @@ -9,8 +33,20 @@ export default function RootLayout({ children: React.ReactNode; }) { return ( - - {children} + + + + + {children} + + + ); } diff --git a/app/(auth)/sign-in/page.tsx b/app/(auth)/sign-in/page.tsx index e12af16f4c..f012c3f358 100644 --- a/app/(auth)/sign-in/page.tsx +++ b/app/(auth)/sign-in/page.tsx @@ -1,7 +1,9 @@ import React from "react"; +import { AuthForm } from "@/components/auth"; + const SignIn = () => { - return
SignIn
; + return ; }; export default SignIn; diff --git a/app/(auth)/sign-up/page.tsx b/app/(auth)/sign-up/page.tsx index b466e40354..dfe04cadeb 100644 --- a/app/(auth)/sign-up/page.tsx +++ b/app/(auth)/sign-up/page.tsx @@ -1,7 +1,9 @@ import React from "react"; +import { AuthForm } from "@/components/auth"; + const SignUp = () => { - return
SignUp
; + return ; }; export default SignUp; diff --git a/auth.config.ts b/auth.config.ts index 7baf25dd7d..58e112ef3c 100644 --- a/auth.config.ts +++ b/auth.config.ts @@ -1,9 +1,28 @@ -import type { NextAuthConfig } from "next-auth"; +import NextAuth, { type NextAuthConfig } from "next-auth"; +import Credentials from "next-auth/providers/credentials"; +import { z } from "zod"; export const authConfig = { pages: { signIn: "/sign-in", - // signUp: "/sign-up", + newUser: "/sign-up", }, - providers: [], + providers: [ + Credentials({ + async authorize(credentials) { + const parsedCredentials = z + .object({ email: z.string().email(), password: z.string().min(6) }) + .safeParse(credentials); + + if (!parsedCredentials.success) return null; + + const { email, password } = parsedCredentials.data; + console.log("AuthConfig.ts"); + console.log({ email, password }); + return null; + }, + }), + ], } satisfies NextAuthConfig; + +export const { signIn, signOut, auth } = NextAuth(authConfig); diff --git a/components/auth/AuthForm.tsx b/components/auth/AuthForm.tsx new file mode 100644 index 0000000000..d1d7403dd1 --- /dev/null +++ b/components/auth/AuthForm.tsx @@ -0,0 +1,141 @@ +"use client"; + +import { Icon } from "@iconify/react"; +import { Button, Checkbox, Divider, Input, Link } from "@nextui-org/react"; +import { useState } from "react"; +import { useFormState } from "react-dom"; + +import { authenticate } from "@/actions"; + +import { ProwlerExtended } from "../icons"; +import { ThemeSwitch } from "../ThemeSwitch"; + +export const AuthForm = ({ type }: { type: string }) => { + const [isVisible, setIsVisible] = useState(false); + + const [state, dispath] = useFormState(authenticate, undefined); + console.log(state); + + const toggleVisibility = () => setIsVisible(!isVisible); + + return ( +
+ {/* Brand Logo and ThemeSwitch */} +
+
+ + +
+
+ + {/* Testimonial */} +
+

+ + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc eget + augue nec massa volutpat aliquet. + +

+
+ + {/* Login Form */} +
+

+ {type === "sign-in" ? "Sign In" : "Sign Up"} +

+
+ + + {isVisible ? ( + + ) : ( + + )} + + } + label="Password" + name="password" + placeholder="Enter your password" + type={isVisible ? "text" : "password"} + variant="bordered" + /> +
+ + Remember me + + + Forgot password? + +
+ + + {type === "sign-in" && ( + <> +
+ +

OR

+ +
+
+ + +
+ + )} + {type === "sign-in" ? ( +

+ Need to create an account?  + Sign Up +

+ ) : ( +

+ Already have an account?  + Log In +

+ )} +
+
+ ); +}; diff --git a/components/auth/index.ts b/components/auth/index.ts new file mode 100644 index 0000000000..1279953728 --- /dev/null +++ b/components/auth/index.ts @@ -0,0 +1 @@ +export * from "./AuthForm"; diff --git a/package-lock.json b/package-lock.json index 0ce80ed402..6352893d57 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,7 +35,8 @@ "server-only": "^0.0.1", "shadcn-ui": "^0.2.3", "tailwind-merge": "^2.5.2", - "tailwindcss-animate": "^1.0.7" + "tailwindcss-animate": "^1.0.7", + "zod": "^3.23.8" }, "devDependencies": { "@iconify/react": "^5.0.1", diff --git a/package.json b/package.json index 8f2d01c7c9..ab09acc019 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,8 @@ "server-only": "^0.0.1", "shadcn-ui": "^0.2.3", "tailwind-merge": "^2.5.2", - "tailwindcss-animate": "^1.0.7" + "tailwindcss-animate": "^1.0.7", + "zod": "^3.23.8" }, "devDependencies": { "@iconify/react": "^5.0.1", From 4b18397e6918ad07c08143dbbca041d9f4b6ce1e Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Wed, 21 Aug 2024 12:32:33 +0200 Subject: [PATCH 147/411] chore: add bcrypt dependency --- package-lock.json | 385 ++++++++++++++++++++++++++++++++++++++++++++-- package.json | 1 + 2 files changed, 372 insertions(+), 14 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6352893d57..d1bff5f4ad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "@tanstack/react-table": "^8.19.3", "add": "^2.0.6", "alert": "^6.0.2", + "bcrypt": "^5.1.1", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "date-fns": "^3.6.0", @@ -582,6 +583,67 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "dependencies": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/@next/env": { "version": "14.2.3", "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.3.tgz", @@ -4422,6 +4484,11 @@ "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", "dev": true }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, "node_modules/acorn": { "version": "8.12.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", @@ -4535,6 +4602,24 @@ "node": ">= 8" } }, + "node_modules/aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" + }, + "node_modules/are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "deprecated": "This package is no longer supported.", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/arg": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", @@ -4836,6 +4921,19 @@ } ] }, + "node_modules/bcrypt": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz", + "integrity": "sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==", + "hasInstallScript": true, + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.11", + "node-addon-api": "^5.0.0" + }, + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -5046,6 +5144,14 @@ "node": ">= 6" } }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "engines": { + "node": ">=10" + } + }, "node_modules/class-variance-authority": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.0.tgz", @@ -5219,6 +5325,14 @@ "simple-swizzle": "^0.2.2" } }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "bin": { + "color-support": "bin.js" + } + }, "node_modules/color2k": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/color2k/-/color2k-2.0.3.tgz", @@ -5246,8 +5360,12 @@ "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 + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" }, "node_modules/cookie": { "version": "0.6.0", @@ -5607,6 +5725,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" + }, + "node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "engines": { + "node": ">=8" + } + }, "node_modules/detect-node-es": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", @@ -6990,11 +7121,32 @@ "node": ">=14.14" } }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "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 + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, "node_modules/function-bind": { "version": "1.1.2", @@ -7031,6 +7183,49 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "deprecated": "This package is no longer supported.", + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/gauge/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/gauge/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "node_modules/gauge/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/get-east-asian-width": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.2.0.tgz", @@ -7286,6 +7481,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -7390,7 +7590,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" @@ -8449,6 +8648,28 @@ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -8515,6 +8736,29 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/mkdirp": { "version": "2.1.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-2.1.6.tgz", @@ -8688,6 +8932,11 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/node-addon-api": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", + "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==" + }, "node_modules/node-domexception": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", @@ -8729,6 +8978,20 @@ "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", "dev": true }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -8771,6 +9034,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "deprecated": "This package is no longer supported.", + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, "node_modules/oauth4webapi": { "version": "2.12.0", "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-2.12.0.tgz", @@ -8934,7 +9209,6 @@ "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" } @@ -9104,7 +9378,6 @@ "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" } @@ -9823,7 +10096,6 @@ "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" }, @@ -9838,7 +10110,6 @@ "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" @@ -9849,7 +10120,6 @@ "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", @@ -9869,7 +10139,6 @@ "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" }, @@ -9982,7 +10251,6 @@ "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" }, @@ -9995,6 +10263,11 @@ "resolved": "https://registry.npmjs.org/server-only/-/server-only-0.0.1.tgz", "integrity": "sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==" }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -10676,6 +10949,41 @@ "node": ">=6" } }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/tar/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -10717,6 +11025,11 @@ "node": ">=8.0" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, "node_modules/ts-api-utils": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", @@ -11067,6 +11380,20 @@ "node": ">= 8" } }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -11160,6 +11487,32 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/wide-align/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/wide-align/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -11259,8 +11612,12 @@ "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 + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/yaml": { "version": "2.4.5", diff --git a/package.json b/package.json index ab09acc019..928b569a63 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "@tanstack/react-table": "^8.19.3", "add": "^2.0.6", "alert": "^6.0.2", + "bcrypt": "^5.1.1", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "date-fns": "^3.6.0", From 063de00e45f2f8798e0bd7b0b257845f7771482d Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Wed, 21 Aug 2024 14:33:53 +0200 Subject: [PATCH 148/411] chore: create --- actions/auth.ts | 1 + components/auth/AuthButton.tsx | 20 ++ components/auth/AuthForm.tsx | 5 +- components/auth/index.ts | 1 + components/providers/ButtonAddProvider.tsx | 4 +- package-lock.json | 396 ++------------------- package.json | 3 +- 7 files changed, 54 insertions(+), 376 deletions(-) create mode 100644 components/auth/AuthButton.tsx diff --git a/actions/auth.ts b/actions/auth.ts index 6523d2f8b4..e6c0968967 100644 --- a/actions/auth.ts +++ b/actions/auth.ts @@ -11,6 +11,7 @@ export async function authenticate( formData: FormData, ) { try { + // await new Promise((resolve) => setTimeout(resolve, 2000)); console.log(formData); await signIn("credentials", formData); } catch (error) { diff --git a/components/auth/AuthButton.tsx b/components/auth/AuthButton.tsx new file mode 100644 index 0000000000..821891f1b2 --- /dev/null +++ b/components/auth/AuthButton.tsx @@ -0,0 +1,20 @@ +"use client"; + +import { Button, CircularProgress } from "@nextui-org/react"; +import React from "react"; +import { useFormStatus } from "react-dom"; + +export const AuthButton = ({ type }: { type: string }) => { + const { pending } = useFormStatus(); + return ( + + ); +}; diff --git a/components/auth/AuthForm.tsx b/components/auth/AuthForm.tsx index d1d7403dd1..63b0e3acd8 100644 --- a/components/auth/AuthForm.tsx +++ b/components/auth/AuthForm.tsx @@ -9,6 +9,7 @@ import { authenticate } from "@/actions"; import { ProwlerExtended } from "../icons"; import { ThemeSwitch } from "../ThemeSwitch"; +import { AuthButton } from "./AuthButton"; export const AuthForm = ({ type }: { type: string }) => { const [isVisible, setIsVisible] = useState(false); @@ -89,9 +90,7 @@ export const AuthForm = ({ type }: { type: string }) => { Forgot password?
- + {type === "sign-in" && ( <> diff --git a/components/auth/index.ts b/components/auth/index.ts index 1279953728..26161c473c 100644 --- a/components/auth/index.ts +++ b/components/auth/index.ts @@ -1 +1,2 @@ +export * from "./AuthButton"; export * from "./AuthForm"; diff --git a/components/providers/ButtonAddProvider.tsx b/components/providers/ButtonAddProvider.tsx index d4b82e1064..30f537ceae 100644 --- a/components/providers/ButtonAddProvider.tsx +++ b/components/providers/ButtonAddProvider.tsx @@ -1,6 +1,6 @@ "use client"; -import { Button } from "@nextui-org/react"; +import { Button, CircularProgress } from "@nextui-org/react"; import React from "react"; import { useFormStatus } from "react-dom"; @@ -8,7 +8,7 @@ export const ButtonAddProvider = () => { const { pending } = useFormStatus(); return ( ); }; diff --git a/package-lock.json b/package-lock.json index d1bff5f4ad..c5b460037b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,7 +20,7 @@ "@tanstack/react-table": "^8.19.3", "add": "^2.0.6", "alert": "^6.0.2", - "bcrypt": "^5.1.1", + "bcryptjs": "^2.4.3", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "date-fns": "^3.6.0", @@ -41,6 +41,7 @@ }, "devDependencies": { "@iconify/react": "^5.0.1", + "@types/bcryptjs": "^2.4.6", "@types/node": "20.5.7", "@types/react": "18.3.3", "@types/react-dom": "18.3.0", @@ -583,67 +584,6 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@mapbox/node-pre-gyp": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", - "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", - "dependencies": { - "detect-libc": "^2.0.0", - "https-proxy-agent": "^5.0.0", - "make-dir": "^3.1.0", - "node-fetch": "^2.6.7", - "nopt": "^5.0.0", - "npmlog": "^5.0.1", - "rimraf": "^3.0.2", - "semver": "^7.3.5", - "tar": "^6.1.11" - }, - "bin": { - "node-pre-gyp": "bin/node-pre-gyp" - } - }, - "node_modules/@mapbox/node-pre-gyp/node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/@mapbox/node-pre-gyp/node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/@mapbox/node-pre-gyp/node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, "node_modules/@next/env": { "version": "14.2.3", "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.3.tgz", @@ -4184,6 +4124,12 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/@types/bcryptjs": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.6.tgz", + "integrity": "sha512-9xlo6R2qDs5uixm0bcIqCeMCE6HiQsIyel9KQySStiyqNl2tnj2mP3DX1Nf56MD6KMenNNlBBsy3LJ7gUEQPXQ==", + "dev": true + }, "node_modules/@types/cookie": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", @@ -4484,11 +4430,6 @@ "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", "dev": true }, - "node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" - }, "node_modules/acorn": { "version": "8.12.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", @@ -4602,24 +4543,6 @@ "node": ">= 8" } }, - "node_modules/aproba": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", - "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" - }, - "node_modules/are-we-there-yet": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", - "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", - "deprecated": "This package is no longer supported.", - "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/arg": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", @@ -4921,18 +4844,10 @@ } ] }, - "node_modules/bcrypt": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz", - "integrity": "sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==", - "hasInstallScript": true, - "dependencies": { - "@mapbox/node-pre-gyp": "^1.0.11", - "node-addon-api": "^5.0.0" - }, - "engines": { - "node": ">= 10.0.0" - } + "node_modules/bcryptjs": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", + "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==" }, "node_modules/binary-extensions": { "version": "2.3.0", @@ -5144,14 +5059,6 @@ "node": ">= 6" } }, - "node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "engines": { - "node": ">=10" - } - }, "node_modules/class-variance-authority": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.0.tgz", @@ -5325,14 +5232,6 @@ "simple-swizzle": "^0.2.2" } }, - "node_modules/color-support": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", - "bin": { - "color-support": "bin.js" - } - }, "node_modules/color2k": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/color2k/-/color2k-2.0.3.tgz", @@ -5360,12 +5259,8 @@ "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" - }, - "node_modules/console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true }, "node_modules/cookie": { "version": "0.6.0", @@ -5725,19 +5620,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" - }, - "node_modules/detect-libc": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", - "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", - "engines": { - "node": ">=8" - } - }, "node_modules/detect-node-es": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", @@ -7121,32 +7003,11 @@ "node": ">=14.14" } }, - "node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/fs-minipass/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "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==" + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true }, "node_modules/function-bind": { "version": "1.1.2", @@ -7183,49 +7044,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/gauge": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", - "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", - "deprecated": "This package is no longer supported.", - "dependencies": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.2", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.1", - "object-assign": "^4.1.1", - "signal-exit": "^3.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/gauge/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "node_modules/gauge/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" - }, - "node_modules/gauge/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/get-east-asian-width": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.2.0.tgz", @@ -7481,11 +7299,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" - }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -7590,6 +7403,7 @@ "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" @@ -8648,28 +8462,6 @@ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, - "node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/make-dir/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -8736,29 +8528,6 @@ "node": ">=16 || 14 >=14.17" } }, - "node_modules/minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minizlib/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/mkdirp": { "version": "2.1.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-2.1.6.tgz", @@ -8932,11 +8701,6 @@ "node": "^10 || ^12 || >=14" } }, - "node_modules/node-addon-api": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", - "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==" - }, "node_modules/node-domexception": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", @@ -8978,20 +8742,6 @@ "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", "dev": true }, - "node_modules/nopt": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", - "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", - "dependencies": { - "abbrev": "1" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -9034,18 +8784,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/npmlog": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", - "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", - "deprecated": "This package is no longer supported.", - "dependencies": { - "are-we-there-yet": "^2.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^3.0.0", - "set-blocking": "^2.0.0" - } - }, "node_modules/oauth4webapi": { "version": "2.12.0", "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-2.12.0.tgz", @@ -9209,6 +8947,7 @@ "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" } @@ -9378,6 +9117,7 @@ "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" } @@ -10096,6 +9836,7 @@ "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" }, @@ -10110,6 +9851,7 @@ "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" @@ -10120,6 +9862,7 @@ "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", @@ -10139,6 +9882,7 @@ "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" }, @@ -10251,6 +9995,7 @@ "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" }, @@ -10263,11 +10008,6 @@ "resolved": "https://registry.npmjs.org/server-only/-/server-only-0.0.1.tgz", "integrity": "sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==" }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" - }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -10949,41 +10689,6 @@ "node": ">=6" } }, - "node_modules/tar": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", - "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", - "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/tar/node_modules/minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/tar/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -11025,11 +10730,6 @@ "node": ">=8.0" } }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, "node_modules/ts-api-utils": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", @@ -11380,20 +11080,6 @@ "node": ">= 8" } }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -11487,32 +11173,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/wide-align": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", - "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", - "dependencies": { - "string-width": "^1.0.2 || 2 || 3 || 4" - } - }, - "node_modules/wide-align/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "node_modules/wide-align/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -11612,12 +11272,8 @@ "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==" - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true }, "node_modules/yaml": { "version": "2.4.5", diff --git a/package.json b/package.json index 928b569a63..10cf838da1 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "@tanstack/react-table": "^8.19.3", "add": "^2.0.6", "alert": "^6.0.2", - "bcrypt": "^5.1.1", + "bcryptjs": "^2.4.3", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "date-fns": "^3.6.0", @@ -33,6 +33,7 @@ }, "devDependencies": { "@iconify/react": "^5.0.1", + "@types/bcryptjs": "^2.4.6", "@types/node": "20.5.7", "@types/react": "18.3.3", "@types/react-dom": "18.3.0", From ed0d975e43af3b770d56c85bfc2392d4bbd20ae1 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Thu, 22 Aug 2024 18:04:30 +0200 Subject: [PATCH 149/411] chore: WIP --- actions/auth.ts | 8 +- auth.config.ts | 19 ++- components/auth/AuthForm.tsx | 156 ++++++++++++++------- components/ui/custom/CustomInput.tsx | 88 ++++++++++++ components/ui/custom/index.ts | 1 + components/ui/form/Form.tsx | 186 ++++++++++++++++++++++++++ components/ui/form/Label.tsx | 26 ++++ components/ui/form/index.ts | 2 + components/ui/sidebar/SidebarWrap.tsx | 2 + lib/index.ts | 1 + lib/seed.ts | 18 +++ package-lock.json | 49 +++++++ package.json | 4 + types/auth.ts | 12 ++ types/index.ts | 1 + 15 files changed, 520 insertions(+), 53 deletions(-) create mode 100644 components/ui/custom/CustomInput.tsx create mode 100644 components/ui/form/Form.tsx create mode 100644 components/ui/form/Label.tsx create mode 100644 components/ui/form/index.ts create mode 100644 lib/seed.ts create mode 100644 types/auth.ts diff --git a/actions/auth.ts b/actions/auth.ts index e6c0968967..34e7fa36c4 100644 --- a/actions/auth.ts +++ b/actions/auth.ts @@ -2,9 +2,7 @@ import { AuthError } from "next-auth"; -import { signIn } from "@/auth.config"; - -// ... +import { signIn, signOut } from "@/auth.config"; export async function authenticate( prevState: string | undefined, @@ -26,3 +24,7 @@ export async function authenticate( throw error; } } + +export async function logOut() { + await signOut(); +} diff --git a/auth.config.ts b/auth.config.ts index 58e112ef3c..1171b2ae42 100644 --- a/auth.config.ts +++ b/auth.config.ts @@ -1,3 +1,4 @@ +import bcryptjs from "bcryptjs"; import NextAuth, { type NextAuthConfig } from "next-auth"; import Credentials from "next-auth/providers/credentials"; import { z } from "zod"; @@ -17,9 +18,21 @@ export const authConfig = { if (!parsedCredentials.success) return null; const { email, password } = parsedCredentials.data; - console.log("AuthConfig.ts"); - console.log({ email, password }); - return null; + console.log({ email, password }, "from AuthConfig.ts"); + + // Check the user using the email + + // const user = await getUser(email); + // if (!user) return null; + + // Compare passwords + + // if (!bcryptjs.compareSync(password, user.password)) return null; + + // Return the user object without the password field + + // const { password: _, ...rest } = user; + // return rest; }, }), ], diff --git a/components/auth/AuthForm.tsx b/components/auth/AuthForm.tsx index 63b0e3acd8..f87458b2c9 100644 --- a/components/auth/AuthForm.tsx +++ b/components/auth/AuthForm.tsx @@ -1,23 +1,47 @@ "use client"; +import { zodResolver } from "@hookform/resolvers/zod"; import { Icon } from "@iconify/react"; import { Button, Checkbox, Divider, Input, Link } from "@nextui-org/react"; import { useState } from "react"; import { useFormState } from "react-dom"; +import { useForm } from "react-hook-form"; +import { z } from "zod"; import { authenticate } from "@/actions"; +import { + Form, + FormControl, + FormField, + FormMessage, +} from "@/components/ui/form"; +import { authFormSchema } from "@/types"; import { ProwlerExtended } from "../icons"; import { ThemeSwitch } from "../ThemeSwitch"; +import { CustomInput } from "../ui/custom"; import { AuthButton } from "./AuthButton"; export const AuthForm = ({ type }: { type: string }) => { - const [isVisible, setIsVisible] = useState(false); + const [user, setUser] = useState(null); const [state, dispath] = useFormState(authenticate, undefined); - console.log(state); - const toggleVisibility = () => setIsVisible(!isVisible); + const form = useForm>({ + resolver: zodResolver(authFormSchema), + defaultValues: { + username: "", + password: "", + }, + }); + + const onSubmit = (values: z.infer) => { + // Do something with the form values + // this will be type-safe and validated + console.log(values); + }; + + console.log(state); return (
{ {/* Testimonial */}
-

+

- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc eget - augue nec massa volutpat aliquet. + Open Cloud Security

- {/* Login Form */}

{type === "sign-in" ? "Sign In" : "Sign Up"}

-
- - - {isVisible ? ( - - ) : ( - - )} - - } - label="Password" - name="password" - placeholder="Enter your password" - type={isVisible ? "text" : "password"} - variant="bordered" - /> -
- - Remember me - - - Forgot password? - -
- - + {/* Sign UP Form */} + {type === "sign-up" && ( +
+ + + + + {/* + {isConfirmVisible ? ( + + ) : ( + + )} + + } + label="Confirm Password" + name="confirmPassword" + placeholder="Confirm your password" + type={isConfirmVisible ? "text" : "password"} + variant="bordered" + /> */} + + I agree with the  + + Terms + +   and  + + Privacy Policy + + + + + + )} + {/* Sign IN Form */} {type === "sign-in" && ( <> +
+ + +
+ + Remember me + + + Forgot password? + +
+ +

OR

@@ -123,6 +184,7 @@ export const AuthForm = ({ type }: { type: string }) => {
)} + {type === "sign-in" ? (

Need to create an account?  diff --git a/components/ui/custom/CustomInput.tsx b/components/ui/custom/CustomInput.tsx new file mode 100644 index 0000000000..07ea994a46 --- /dev/null +++ b/components/ui/custom/CustomInput.tsx @@ -0,0 +1,88 @@ +"use client"; + +import { Icon } from "@iconify/react"; +import { Input } from "@nextui-org/react"; +import React, { useState } from "react"; +import { Control, FieldPath } from "react-hook-form"; +import { z } from "zod"; + +import { FormControl, FormField, FormMessage } from "@/components/ui/form"; +import { authFormSchema } from "@/types"; + +type CustomInputProps = + | { + control: Control>; + name: FieldPath>; + password?: false; + label: string; + type: "text" | "email"; + placeholder: string; + isRequired?: boolean; + } + | { + control: Control>; + password: true; + name?: never; + label?: never; + type?: never; + placeholder?: never; + isRequired?: never; + }; + +export const CustomInput = ({ + control, + name, + type, + label, + placeholder, + password = false, + isRequired = true, +}: CustomInputProps) => { + const [isVisible, setIsVisible] = useState(false); + + const toggleVisibility = () => setIsVisible(!isVisible); + + const inputProps = password + ? { + name: "password", + label: "Password", + type: isVisible ? "text" : "password", + placeholder: "Enter your password", + isRequired: true, + } + : { name, label, type, placeholder, isRequired }; + + return ( + ( + <> + + + + + ) + } + /> + + + + )} + /> + ); +}; diff --git a/components/ui/custom/index.ts b/components/ui/custom/index.ts index 00732c48c8..126110ade6 100644 --- a/components/ui/custom/index.ts +++ b/components/ui/custom/index.ts @@ -1 +1,2 @@ export * from "./CustomBox"; +export * from "./CustomInput"; diff --git a/components/ui/form/Form.tsx b/components/ui/form/Form.tsx new file mode 100644 index 0000000000..c598b08b8f --- /dev/null +++ b/components/ui/form/Form.tsx @@ -0,0 +1,186 @@ +"use client"; + +import * as LabelPrimitive from "@radix-ui/react-label"; +import { Slot } from "@radix-ui/react-slot"; +import * as React from "react"; +import { + Controller, + ControllerProps, + FieldPath, + FieldValues, + FormProvider, + useFormContext, +} from "react-hook-form"; + +import { cn } from "@/lib/utils"; + +import { Label } from "./label"; + +const Form = FormProvider; + +type FormFieldContextValue< + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath = FieldPath, +> = { + name: TName; +}; + +const FormFieldContext = React.createContext( + {} as FormFieldContextValue, +); + +const FormField = < + TFieldValues extends FieldValues = FieldValues, + TName extends FieldPath = FieldPath, +>({ + ...props +}: ControllerProps) => { + return ( + + + + ); +}; + +const useFormField = () => { + const fieldContext = React.useContext(FormFieldContext); + const itemContext = React.useContext(FormItemContext); + const { getFieldState, formState } = useFormContext(); + + const fieldState = getFieldState(fieldContext.name, formState); + + if (!fieldContext) { + throw new Error("useFormField should be used within "); + } + + const { id } = itemContext; + + return { + id, + name: fieldContext.name, + formItemId: `${id}-form-item`, + formDescriptionId: `${id}-form-item-description`, + formMessageId: `${id}-form-item-message`, + ...fieldState, + }; +}; + +type FormItemContextValue = { + id: string; +}; + +const FormItemContext = React.createContext( + {} as FormItemContextValue, +); + +const FormItem = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => { + const id = React.useId(); + + return ( + +

+ + ); +}); +FormItem.displayName = "FormItem"; + +const FormLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => { + const { error, formItemId } = useFormField(); + + return ( +
+ ); + }, + }, +]; diff --git a/components/users/table/DataTableColumnHeader.tsx b/components/users/table/DataTableColumnHeader.tsx new file mode 100644 index 0000000000..7fe6027527 --- /dev/null +++ b/components/users/table/DataTableColumnHeader.tsx @@ -0,0 +1,49 @@ +import { Button } from "@nextui-org/react"; +import { Column } from "@tanstack/react-table"; +import { + ArrowDownIcon, + ArrowUpIcon, + ChevronsLeftRightIcon, +} from "lucide-react"; +import { HTMLAttributes } from "react"; + +interface DataTableColumnHeaderProps + extends HTMLAttributes { + column: Column; + title: string; +} + +export const DataTableColumnHeader = ({ + column, + title, + className, +}: DataTableColumnHeaderProps) => { + const renderSortIcon = () => { + const sort = column.getIsSorted(); + if (!sort) { + return ; + } + return sort === "desc" ? ( + + ) : ( + + ); + }; + + if (!column.getCanSort()) { + return
{title}
; + } + return ( +
+ +
+ ); +}; diff --git a/components/users/table/DataTablePagination.tsx b/components/users/table/DataTablePagination.tsx new file mode 100644 index 0000000000..0e195f0507 --- /dev/null +++ b/components/users/table/DataTablePagination.tsx @@ -0,0 +1,85 @@ +"use client"; + +import { + ChevronLeftIcon, + ChevronRightIcon, + DoubleArrowLeftIcon, + DoubleArrowRightIcon, +} from "@radix-ui/react-icons"; +import Link from "next/link"; +import { usePathname, useSearchParams } from "next/navigation"; + +import { extractPaginationInfo } from "@/lib"; +import { MetaDataProps } from "@/types"; + +interface DataTablePaginationProps { + pageSizeOptions?: number[]; + metadata?: MetaDataProps; +} + +export function DataTablePagination({ metadata }: DataTablePaginationProps) { + if (!metadata) return null; + const pathname = usePathname(); + const searchParams = useSearchParams(); + + const { currentPage, totalPages, totalEntries } = + extractPaginationInfo(metadata); + + const createPageUrl = (pageNumber: number | string) => { + const params = new URLSearchParams(searchParams); + + if (pageNumber === "...") return `${pathname}?${params.toString()}`; + + if (+pageNumber > totalPages) { + return `${pathname}?${params.toString()}`; + } + + params.set("page", pageNumber.toString()); + return `${pathname}?${params.toString()}`; + }; + + return ( +
+
+ {totalEntries} entries in Total. +
+
+
+ Page {currentPage} of {totalPages} +
+
+ +
+
+
+ ); +} diff --git a/components/users/table/DataTableUser.tsx b/components/users/table/DataTableUser.tsx new file mode 100644 index 0000000000..7b5c6a5049 --- /dev/null +++ b/components/users/table/DataTableUser.tsx @@ -0,0 +1,106 @@ +"use client"; + +import { + ColumnDef, + flexRender, + getCoreRowModel, + getPaginationRowModel, + getSortedRowModel, + SortingState, + useReactTable, +} from "@tanstack/react-table"; +import { useState } from "react"; + +import { DataTablePagination } from "@/components/providers"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui"; +import { MetaDataProps } from "@/types"; + +interface DataTableUserProps { + columns: ColumnDef[]; + data: TData[]; + metadata?: MetaDataProps; +} + +export function DataTableUser({ + columns, + data, + metadata, +}: DataTableUserProps) { + const [sorting, setSorting] = useState([]); + const table = useReactTable({ + data, + columns, + enableSorting: true, + getCoreRowModel: getCoreRowModel(), + getPaginationRowModel: getPaginationRowModel(), + onSortingChange: setSorting, + getSortedRowModel: getSortedRowModel(), + state: { + sorting, + }, + }); + return ( + <> +
+
+ + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext(), + )} + + ); + })} + + ))} + + + {table.getRowModel().rows?.length ? ( + table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender( + cell.column.columnDef.cell, + cell.getContext(), + )} + + ))} + + )) + ) : ( + + + No results. + + + )} + +
+
+
+ +
+ + ); +} diff --git a/components/users/table/SkeletonTableUser.tsx b/components/users/table/SkeletonTableUser.tsx new file mode 100644 index 0000000000..cf4a584428 --- /dev/null +++ b/components/users/table/SkeletonTableUser.tsx @@ -0,0 +1,59 @@ +import { Card, Skeleton } from "@nextui-org/react"; +import React from "react"; + +export const SkeletonTableUser = () => { + return ( + + {/* Table headers */} +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+ + {/* Table body */} +
+ {[...Array(10)].map((_, index) => ( +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+ ))} +
+
+ ); +}; diff --git a/dataUsers.json b/dataUsers.json new file mode 100644 index 0000000000..26428a633a --- /dev/null +++ b/dataUsers.json @@ -0,0 +1,98 @@ +{ + "links": { + "first": "http://localhost:8080/api/v1/users?page%5Bnumber%5D=1", + "last": "http://localhost:8080/api/v1/users?page%5Bnumber%5D=1", + "next": null, + "prev": null + }, + "data": [ + { + "id": "001", + "email": "john.doe@example.com", + "name": "John Doe", + "role": "Admin", + "dateAdded": "2024-08-14T10:00:00.000000Z", + "status": "active" + }, + { + "id": "002", + "email": "jane.smith@example.com", + "name": "Jane Smith", + "role": "User", + "dateAdded": "2024-08-15T11:30:00.000000Z", + "status": "inactive" + }, + { + "id": "003", + "email": "will.johnson@example.com", + "name": "Will Johnson", + "role": "Admin", + "dateAdded": "2024-08-16T09:15:00.000000Z", + "status": "active" + }, + { + "id": "004", + "email": "emily.davis@example.com", + "name": "Emily Davis", + "role": "User", + "dateAdded": "2024-08-17T14:00:00.000000Z", + "status": "active" + }, + { + "id": "005", + "email": "michael.brown@example.com", + "name": "Michael Brown", + "role": "User", + "dateAdded": "2024-08-18T08:45:00.000000Z", + "status": "inactive" + }, + { + "id": "006", + "email": "sarah.miller@example.com", + "name": "Sarah Miller", + "role": "Admin", + "dateAdded": "2024-08-19T13:25:00.000000Z", + "status": "active" + }, + { + "id": "007", + "email": "david.wilson@example.com", + "name": "David Wilson", + "role": "User", + "dateAdded": "2024-08-20T10:50:00.000000Z", + "status": "active" + }, + { + "id": "008", + "email": "lisa.moore@example.com", + "name": "Lisa Moore", + "role": "Admin", + "dateAdded": "2024-08-21T07:30:00.000000Z", + "status": "inactive" + }, + { + "id": "009", + "email": "james.taylor@example.com", + "name": "James Taylor", + "role": "User", + "dateAdded": "2024-08-22T12:10:00.000000Z", + "status": "active" + }, + { + "id": "010", + "email": "anna.anderson@example.com", + "name": "Anna Anderson", + "role": "User", + "dateAdded": "2024-08-23T11:00:00.000000Z", + "status": "inactive" + } + ], + "meta": { + "pagination": { + "page": 1, + "pages": 1, + "count": 10 + }, + "version": "v1" + } +} diff --git a/types/components.ts b/types/components.ts index 5580d151eb..0fc88a2190 100644 --- a/types/components.ts +++ b/types/components.ts @@ -80,3 +80,12 @@ export interface MetaDataProps { }; version: string; } + +export interface UserProps { + id: string; + email: string; + name: string; + role: string; + dateAdded: string; + status: "active" | "inactive"; +} From 4cf5d9cb43f5b66c80af98e01896fa4b5e8f06c7 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Fri, 23 Aug 2024 23:00:41 +0200 Subject: [PATCH 151/411] chore: WIP --- components/auth/AuthForm.tsx | 149 +++++++++++++++------------ components/ui/custom/CustomInput.tsx | 38 ++++--- types/auth.ts | 35 +++++-- 3 files changed, 126 insertions(+), 96 deletions(-) diff --git a/components/auth/AuthForm.tsx b/components/auth/AuthForm.tsx index f87458b2c9..599a2a280c 100644 --- a/components/auth/AuthForm.tsx +++ b/components/auth/AuthForm.tsx @@ -2,7 +2,7 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { Icon } from "@iconify/react"; -import { Button, Checkbox, Divider, Input, Link } from "@nextui-org/react"; +import { Button, Checkbox, Divider, Link } from "@nextui-org/react"; import { useState } from "react"; import { useFormState } from "react-dom"; import { useForm } from "react-hook-form"; @@ -25,24 +25,23 @@ import { AuthButton } from "./AuthButton"; export const AuthForm = ({ type }: { type: string }) => { const [user, setUser] = useState(null); - const [state, dispath] = useFormState(authenticate, undefined); + const formSchema = authFormSchema(type); + // const [state, dispath] = useFormState(authenticate, undefined); - const form = useForm>({ - resolver: zodResolver(authFormSchema), + const form = useForm>({ + resolver: zodResolver(formSchema), defaultValues: { - username: "", + email: "", password: "", }, }); - const onSubmit = (values: z.infer) => { + const onSubmit = (values: z.infer) => { // Do something with the form values // this will be type-safe and validated console.log(values); }; - console.log(state); - return (
{

{type === "sign-in" ? "Sign In" : "Sign Up"}

- {/* Sign UP Form */} - {type === "sign-up" && ( -
- - - - - {/* + + {type === "sign-up" && ( + <> + + + + )} + + + + {type === "sign-in" && ( +
+ + Remember me + + + Forgot password? + +
+ )} + {/* @@ -119,42 +139,40 @@ export const AuthForm = ({ type }: { type: string }) => { type={isConfirmVisible ? "text" : "password"} variant="bordered" /> */} - - I agree with the  - - Terms - -   and  - - Privacy Policy - - - - - - )} - {/* Sign IN Form */} + {type === "sign-up" && ( + ( + <> + + + I agree with the  + + Terms + +   and  + + Privacy Policy + + + + + + )} + /> + )} + + + + {type === "sign-in" && ( <> -
- - -
- - Remember me - - - Forgot password? - -
- -

OR

@@ -184,7 +202,6 @@ export const AuthForm = ({ type }: { type: string }) => {
)} - {type === "sign-in" ? (

Need to create an account?  diff --git a/components/ui/custom/CustomInput.tsx b/components/ui/custom/CustomInput.tsx index 07ea994a46..b1d57a5a29 100644 --- a/components/ui/custom/CustomInput.tsx +++ b/components/ui/custom/CustomInput.tsx @@ -9,18 +9,20 @@ import { z } from "zod"; import { FormControl, FormField, FormMessage } from "@/components/ui/form"; import { authFormSchema } from "@/types"; +const formSchema = authFormSchema("sign-up"); + type CustomInputProps = | { - control: Control>; - name: FieldPath>; - password?: false; + control: Control>; + name: FieldPath>; label: string; type: "text" | "email"; placeholder: string; isRequired?: boolean; + password?: false; } | { - control: Control>; + control: Control>; password: true; name?: never; label?: never; @@ -42,32 +44,28 @@ export const CustomInput = ({ const toggleVisibility = () => setIsVisible(!isVisible); - const inputProps = password - ? { - name: "password", - label: "Password", - type: isVisible ? "text" : "password", - placeholder: "Enter your password", - isRequired: true, - } - : { name, label, type, placeholder, isRequired }; + const inputName = password ? "password" : name!; + const inputLabel = password ? "Password" : label; + const inputType = password ? (isVisible ? "text" : "password") : type; + const inputPlaceholder = password ? "Enter your password" : placeholder; + const inputIsRequired = password ? true : isRequired; return ( >} render={({ field }) => ( <> - ) + ) : null } /> diff --git a/types/auth.ts b/types/auth.ts index f359f53883..2f1676d945 100644 --- a/types/auth.ts +++ b/types/auth.ts @@ -1,12 +1,27 @@ import { z } from "zod"; -export const authFormSchema = z.object({ - username: z - .string() - .min(4, { - message: "Username must be at least 4 characters.", - }) - .max(20), - password: z.string().min(6), - email: z.string().email(), -}); +export const authFormSchema = (type: string) => + z.object({ + // Sign Up + companyName: type === "sign-in" ? z.string().optional() : z.string().min(3), + firstName: + type === "sign-in" + ? z.string().optional() + : z + .string() + .min(3, { + message: "The name must be at least 3 characters.", + }) + .max(20), + termsAndConditions: + type === "sign-in" + ? z.literal(true).optional() + : z.literal(true, { + errorMap: () => ({ + message: "You must accept the terms and conditions.", + }), + }), + // both + email: z.string().email(), + password: z.string().min(6), + }); From 440e95515a01fbad63cd5616b4b807e3893198cb Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Mon, 26 Aug 2024 15:33:07 +0200 Subject: [PATCH 152/411] feat: add new items to the main menu --- app/(prowler)/categories/page.tsx | 12 ++++++++ .../{integration => integrations}/page.tsx | 4 +-- app/(prowler)/workloads/page.tsx | 12 ++++++++ components/ui/sidebar/SidebarItems.tsx | 30 +++++++++++++------ 4 files changed, 47 insertions(+), 11 deletions(-) create mode 100644 app/(prowler)/categories/page.tsx rename app/(prowler)/{integration => integrations}/page.tsx (59%) create mode 100644 app/(prowler)/workloads/page.tsx diff --git a/app/(prowler)/categories/page.tsx b/app/(prowler)/categories/page.tsx new file mode 100644 index 0000000000..1a5650d8af --- /dev/null +++ b/app/(prowler)/categories/page.tsx @@ -0,0 +1,12 @@ +import { Spacer } from "@nextui-org/react"; + +import { Header } from "@/components/ui"; + +export default async function Categories() { + return ( + <> +

+ + + ); +} diff --git a/app/(prowler)/integration/page.tsx b/app/(prowler)/integrations/page.tsx similarity index 59% rename from app/(prowler)/integration/page.tsx rename to app/(prowler)/integrations/page.tsx index 576089371b..a930f1ed36 100644 --- a/app/(prowler)/integration/page.tsx +++ b/app/(prowler)/integrations/page.tsx @@ -2,10 +2,10 @@ import React from "react"; import { Header } from "@/components/ui"; -export default function Integration() { +export default function Integrations() { return ( <> -
+

Hi hi from Integration page

diff --git a/app/(prowler)/workloads/page.tsx b/app/(prowler)/workloads/page.tsx new file mode 100644 index 0000000000..3cadd0d1ca --- /dev/null +++ b/app/(prowler)/workloads/page.tsx @@ -0,0 +1,12 @@ +import { Spacer } from "@nextui-org/react"; + +import { Header } from "@/components/ui"; + +export default async function Workloads() { + return ( + <> +
+ + + ); +} diff --git a/components/ui/sidebar/SidebarItems.tsx b/components/ui/sidebar/SidebarItems.tsx index 20cf4d3533..43f01dfa66 100644 --- a/components/ui/sidebar/SidebarItems.tsx +++ b/components/ui/sidebar/SidebarItems.tsx @@ -126,12 +126,6 @@ export const sectionItems: SidebarItem[] = [ // /> // ), // }, - { - key: "services", - href: "/services", - icon: "material-symbols:linked-services-outline", - title: "Services", - }, { key: "compliance", href: "/compliance", @@ -143,6 +137,24 @@ export const sectionItems: SidebarItem[] = [ ), }, + { + key: "services", + href: "/services", + icon: "material-symbols:linked-services-outline", + title: "Services", + }, + { + key: "categories", + href: "/categories", + icon: "material-symbols:folder-open-outline", + title: "Categories", + }, + { + key: "workloads", + href: "/workloads", + icon: "lucide:tags", + title: "Workloads", + }, ], }, { @@ -190,10 +202,10 @@ export const sectionItems: SidebarItem[] = [ ), }, { - key: "integration", - href: "/integration", + key: "integrations", + href: "/integrations", icon: "tabler:puzzle", - title: "Integration", + title: "Integrations", }, { key: "settings", From 1985b16824009e4a8d79f6bdb5b2d9491488fe95 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Tue, 27 Aug 2024 17:05:09 +0200 Subject: [PATCH 153/411] feat: add nexthauth.d.ts to have the DefaultSession info available --- nextauth.d.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 nextauth.d.ts diff --git a/nextauth.d.ts b/nextauth.d.ts new file mode 100644 index 0000000000..052e4d4a7c --- /dev/null +++ b/nextauth.d.ts @@ -0,0 +1,14 @@ +import { DefaultSession } from "next-auth"; + +declare module "next-auth" { + interface Session { + user: { + id: string; + firstName: string; + companyName: string; + email: string; + role: string; + image?: string; + } & DefaultSession["user"]; + } +} From b5a40d07cfe01e1eaccd03adbf62cd766b3c9d2a Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Tue, 27 Aug 2024 18:37:45 +0200 Subject: [PATCH 154/411] feat: Nextauth is working --- actions/auth.ts | 42 +- app/(auth)/layout.tsx | 10 +- app/(prowler)/profile/page.tsx | 24 + app/api/auth/[...nextauth]/route.ts | 3 + app/providers.tsx | 9 +- auth.config.ts | 83 +- components/auth/AuthButton.tsx | 3 +- components/auth/AuthForm.tsx | 53 +- components/ui/custom/CustomInput.tsx | 38 +- components/ui/form/Form.tsx | 2 +- components/ui/sidebar/SidebarWrap.tsx | 17 +- lib/seed.ts | 37 +- middleware.ts | 10 + package-lock.json | 2019 ++++++++++++++++++------- package.json | 4 +- types/auth.ts | 4 +- 16 files changed, 1728 insertions(+), 630 deletions(-) create mode 100644 app/(prowler)/profile/page.tsx create mode 100644 app/api/auth/[...nextauth]/route.ts create mode 100644 middleware.ts diff --git a/actions/auth.ts b/actions/auth.ts index 34e7fa36c4..3c93021a12 100644 --- a/actions/auth.ts +++ b/actions/auth.ts @@ -3,25 +3,47 @@ import { AuthError } from "next-auth"; import { signIn, signOut } from "@/auth.config"; +// import { authFormSchema } from "@/types"; -export async function authenticate( - prevState: string | undefined, - formData: FormData, -) { +// const formSchema = authFormSchema("sign-in"); + +const defaultValues = { + email: "", + password: "", +}; + +// Fix TS types. +export async function authenticate(prevState: any, formData: any) { try { - // await new Promise((resolve) => setTimeout(resolve, 2000)); - console.log(formData); - await signIn("credentials", formData); + await new Promise((resolve) => setTimeout(resolve, 2000)); + await signIn("credentials", { + ...formData, + redirect: false, + }); + return { + message: "Success", + }; } catch (error) { if (error instanceof AuthError) { switch (error.type) { case "CredentialsSignin": - return "Invalid credentials."; + return { + message: "Credentials error", + errors: { + ...defaultValues, + credentials: "Incorrect email or password", + }, + }; default: - return "Something went wrong."; + return { + message: "Unknown error", + errors: { + ...defaultValues, + unknown: "Unknown error", + }, + }; } } - throw error; } } diff --git a/app/(auth)/layout.tsx b/app/(auth)/layout.tsx index f1f003744e..a9fdc8c394 100644 --- a/app/(auth)/layout.tsx +++ b/app/(auth)/layout.tsx @@ -1,7 +1,9 @@ import "@/styles/globals.css"; import { Metadata, Viewport } from "next"; +import { redirect } from "next/navigation"; +import { auth } from "@/auth.config"; import { Toaster } from "@/components/ui"; import { fontSans } from "@/config/fonts"; import { siteConfig } from "@/config/site"; @@ -27,11 +29,17 @@ export const viewport: Viewport = { ], }; -export default function RootLayout({ +export default async function RootLayout({ children, }: { children: React.ReactNode; }) { + const session = await auth(); + + if (session?.user) { + redirect("/"); + } + return ( diff --git a/app/(prowler)/profile/page.tsx b/app/(prowler)/profile/page.tsx new file mode 100644 index 0000000000..76c72a8800 --- /dev/null +++ b/app/(prowler)/profile/page.tsx @@ -0,0 +1,24 @@ +import { Spacer } from "@nextui-org/react"; +import { redirect } from "next/navigation"; +import React from "react"; + +import { auth } from "@/auth.config"; +import { Header } from "@/components/ui"; + +export default async function Profile() { + const session = await auth(); + + if (!session?.user) { + // redirect("/sign-in?returnTo=/profile"); + redirect("/sign-in"); + } + + return ( + <> +
+ + +
{JSON.stringify(session.user, null, 2)}
+ + ); +} diff --git a/app/api/auth/[...nextauth]/route.ts b/app/api/auth/[...nextauth]/route.ts new file mode 100644 index 0000000000..316cf49c14 --- /dev/null +++ b/app/api/auth/[...nextauth]/route.ts @@ -0,0 +1,3 @@ +import { handlers } from "@/auth.config"; + +export const { GET, POST } = handlers; diff --git a/app/providers.tsx b/app/providers.tsx index 294f0647a6..942bce43dc 100644 --- a/app/providers.tsx +++ b/app/providers.tsx @@ -2,6 +2,7 @@ import { NextUIProvider } from "@nextui-org/system"; import { useRouter } from "next/navigation"; +import { SessionProvider } from "next-auth/react"; import { ThemeProvider as NextThemesProvider } from "next-themes"; import { ThemeProviderProps } from "next-themes/dist/types"; import * as React from "react"; @@ -15,8 +16,10 @@ export function Providers({ children, themeProps }: ProvidersProps) { const router = useRouter(); return ( - - {children} - + + + {children} + + ); } diff --git a/auth.config.ts b/auth.config.ts index 1171b2ae42..587b64197f 100644 --- a/auth.config.ts +++ b/auth.config.ts @@ -3,39 +3,86 @@ import NextAuth, { type NextAuthConfig } from "next-auth"; import Credentials from "next-auth/providers/credentials"; import { z } from "zod"; +import { userMockData } from "./lib"; + +async function getUser(email: string, password: string): Promise { + // Check if the user exists in the userMockData array. + const user = userMockData.find((user) => user.email === email); + if (!user) return null; + + if (!bcryptjs.compareSync(password, user.password)) return null; + + return { + id: user.id, + name: user.name, + companyName: user.companyName, + email: user.email, + role: user.role, + image: user.image, + }; +} + export const authConfig = { + session: { + strategy: "jwt", + }, pages: { signIn: "/sign-in", newUser: "/sign-up", }, + callbacks: { + authorized({ auth, request: { nextUrl } }) { + const isLoggedIn = !!auth?.user; + // console.log({ auth }, "llegas o no"); + const isOnDashboard = nextUrl.pathname.startsWith("/"); + if (isOnDashboard) { + if (isLoggedIn) return true; + return false; // Redirect unauthenticated users to login page + } else if (isLoggedIn) { + return Response.redirect(new URL("/", nextUrl)); + } + return true; + }, + + jwt({ token, user }) { + if (user) { + token.data = user; + } + return token; + }, + + session({ session, token }) { + session.user = token.data as any; + return session; + }, + }, providers: [ Credentials({ + name: "credentials", + credentials: { + email: { label: "email", type: "text" }, + password: { label: "password", type: "password" }, + }, async authorize(credentials) { const parsedCredentials = z - .object({ email: z.string().email(), password: z.string().min(6) }) + .object({ + email: z.string().email(), + password: z.string().min(6), + }) .safeParse(credentials); - if (!parsedCredentials.success) return null; - + if (!parsedCredentials.success) { + return null; + } const { email, password } = parsedCredentials.data; - console.log({ email, password }, "from AuthConfig.ts"); - // Check the user using the email - - // const user = await getUser(email); - // if (!user) return null; - - // Compare passwords - - // if (!bcryptjs.compareSync(password, user.password)) return null; - - // Return the user object without the password field - - // const { password: _, ...rest } = user; - // return rest; + const user = await getUser(email, password); + if (!user) return null; + // console.log({ user }); + return user; }, }), ], } satisfies NextAuthConfig; -export const { signIn, signOut, auth } = NextAuth(authConfig); +export const { signIn, signOut, auth, handlers } = NextAuth(authConfig); diff --git a/components/auth/AuthButton.tsx b/components/auth/AuthButton.tsx index 821891f1b2..0eda81782a 100644 --- a/components/auth/AuthButton.tsx +++ b/components/auth/AuthButton.tsx @@ -6,10 +6,11 @@ import { useFormStatus } from "react-dom"; export const AuthButton = ({ type }: { type: string }) => { const { pending } = useFormStatus(); + return ( + ); + return ( - - - ) : null - } /> - + )} /> diff --git a/components/ui/form/Form.tsx b/components/ui/form/Form.tsx index c598b08b8f..0e6296cfd4 100644 --- a/components/ui/form/Form.tsx +++ b/components/ui/form/Form.tsx @@ -14,7 +14,7 @@ import { import { cn } from "@/lib/utils"; -import { Label } from "./label"; +import { Label } from "./Label"; const Form = FormProvider; diff --git a/components/ui/sidebar/SidebarWrap.tsx b/components/ui/sidebar/SidebarWrap.tsx index 57243d92de..fe35f5f1c5 100644 --- a/components/ui/sidebar/SidebarWrap.tsx +++ b/components/ui/sidebar/SidebarWrap.tsx @@ -3,7 +3,9 @@ import { Icon } from "@iconify/react"; import { Button, ScrollShadow, Spacer, Tooltip } from "@nextui-org/react"; import clsx from "clsx"; +import Link from "next/link"; import { usePathname } from "next/navigation"; +import { useSession } from "next-auth/react"; import React, { useCallback } from "react"; import { useMediaQuery } from "usehooks-ts"; @@ -21,6 +23,8 @@ import { UserAvatar } from "./UserAvatar"; export const SidebarWrap = () => { const pathname = usePathname(); + const { data: session } = useSession(); + const [isCollapsed, setIsCollapsed] = useLocalStorage("isCollapsed", false); const isMobile = useMediaQuery("(max-width: 768px)"); @@ -65,11 +69,13 @@ export const SidebarWrap = () => {
- + + {" "} + { )} + + + + + Add User + + setOpen(false)}> +
+ + +
+
+ +
+ +
+ + ); +}; diff --git a/components/users/ButtonAddUser.tsx b/components/users/ButtonAddUser.tsx new file mode 100644 index 0000000000..e840dc0157 --- /dev/null +++ b/components/users/ButtonAddUser.tsx @@ -0,0 +1,14 @@ +"use client"; + +import { Button } from "@nextui-org/react"; +import React from "react"; +import { useFormStatus } from "react-dom"; + +export const ButtonAddUser = () => { + const { pending } = useFormStatus(); + return ( + + ); +}; diff --git a/components/users/ButtonEditUser.tsx b/components/users/ButtonEditUser.tsx new file mode 100644 index 0000000000..a0322b7a2d --- /dev/null +++ b/components/users/ButtonEditUser.tsx @@ -0,0 +1,14 @@ +"use client"; + +import { Button } from "@nextui-org/react"; +import React from "react"; +import { useFormStatus } from "react-dom"; + +export const ButtonEditUser = () => { + const { pending } = useFormStatus(); + return ( + + ); +}; diff --git a/components/users/CustomSelectUser.tsx b/components/users/CustomSelectUser.tsx new file mode 100644 index 0000000000..10acfc973c --- /dev/null +++ b/components/users/CustomSelectUser.tsx @@ -0,0 +1,39 @@ +import { + Label, + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui"; +import { UserProps } from "@/types"; + +interface CustomSelectUserProps { + userData?: UserProps; +} + +export const CustomSelectUser: React.FC = ({ + userData, +}) => { + return ( + <> + + + + ); +}; diff --git a/components/users/EditUserModal.tsx b/components/users/EditUserModal.tsx new file mode 100644 index 0000000000..0d64c7f940 --- /dev/null +++ b/components/users/EditUserModal.tsx @@ -0,0 +1,82 @@ +"use client"; + +import { Input } from "@nextui-org/react"; +import { useRef } from "react"; + +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, +} from "@/components/ui"; +import { UserProps } from "@/types"; + +import { ButtonEditUser } from "./ButtonEditUser"; +import { CustomSelectUser } from "./CustomSelectUser"; + +interface EditUserModalProps { + isOpen: boolean; + setIsOpen: React.Dispatch>; + userData: UserProps; +} + +export const EditUserModal: React.FC = ({ + isOpen, + setIsOpen, + userData, +}) => { + const ref = useRef(null); + + return ( + + + + Edit User + +
setIsOpen(false)}> +
+ + + +
+
+ +
+
+
+
+ ); +}; diff --git a/components/users/index.ts b/components/users/index.ts index a458fa1122..36f6d8f557 100644 --- a/components/users/index.ts +++ b/components/users/index.ts @@ -1,5 +1,11 @@ +export * from "./AddUserModal"; +export * from "./ButtonAddUser"; +export * from "./ButtonEditUser"; +export * from "./CustomSelectUser"; +export * from "./EditUserModal"; export * from "./table/ColumnsUser"; export * from "./table/DataTableColumnHeader"; export * from "./table/DataTablePagination"; export * from "./table/DataTableUser"; export * from "./table/SkeletonTableUser"; +export * from "./table/UserActions"; diff --git a/components/users/table/ColumnsUser.tsx b/components/users/table/ColumnsUser.tsx index 4dbf8001f0..70a05c189a 100644 --- a/components/users/table/ColumnsUser.tsx +++ b/components/users/table/ColumnsUser.tsx @@ -1,17 +1,10 @@ "use client"; -import { - Button, - Dropdown, - DropdownItem, - DropdownMenu, - DropdownTrigger, -} from "@nextui-org/react"; import { ColumnDef } from "@tanstack/react-table"; -import { VerticalDotsIcon } from "@/components/icons"; import { DateWithTime } from "@/components/providers"; import { StatusBadge } from "@/components/ui"; +import { UserActions } from "@/components/users"; import { UserProps } from "@/types"; const getUserData = (row: { original: UserProps }) => { @@ -63,21 +56,9 @@ export const ColumnsUser: ColumnDef[] = [ accessorKey: "actions", header: () =>
Actions
, id: "actions", - cell: () => { - return ( -
- - - - - - Edit - - -
- ); + cell: ({ row }) => { + const userData = getUserData(row); + return ; }, }, ]; diff --git a/components/users/table/UserActions.tsx b/components/users/table/UserActions.tsx new file mode 100644 index 0000000000..9ff00eda1d --- /dev/null +++ b/components/users/table/UserActions.tsx @@ -0,0 +1,54 @@ +"use client"; + +import { useState } from "react"; + +import { VerticalDotsIcon } from "@/components/icons"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui"; +import { EditUserModal } from "@/components/users"; +import { UserProps } from "@/types"; + +interface UserActionsProps { + userData: UserProps; +} + +export const UserActions: React.FC = ({ userData }) => { + const [isEditOpen, setIsEditOpen] = useState(false); + + return ( + <> +
+ + + + + + setIsEditOpen(true)} + > + Edit + + + Delete + + + +
+ {isEditOpen && ( + + )} + + ); +}; diff --git a/package-lock.json b/package-lock.json index 689b2cbfb6..320beae819 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@nextui-org/system": "2.2.1", "@nextui-org/theme": "2.2.5", "@radix-ui/react-dialog": "^1.1.1", + "@radix-ui/react-dropdown-menu": "^2.1.1", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-label": "^2.1.0", "@radix-ui/react-select": "^2.1.1", @@ -2886,6 +2887,35 @@ } } }, + "node_modules/@radix-ui/react-dropdown-menu": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.1.tgz", + "integrity": "sha512-y8E+x9fBq9qvteD2Zwa4397pUVhYsh9iq44b5RD5qu1GMJWBCBuVg1hMyItbc6+zH00TxGRqd9Iot4wzf3OoBQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-menu": "2.1.1", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-controllable-state": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-focus-guards": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.0.tgz", @@ -2953,6 +2983,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.0.tgz", "integrity": "sha512-peLblDlFw/ngk3UWq0VnYaOLy6agTZZ+MUO/WhVfm14vJGML+xH4FAl2XQGLqdefjNb7ApRg6Yn7U42ZhmYXdw==", + "license": "MIT", "dependencies": { "@radix-ui/react-primitive": "2.0.0" }, @@ -2971,6 +3002,71 @@ } } }, + "node_modules/@radix-ui/react-menu": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.1.tgz", + "integrity": "sha512-oa3mXRRVjHi6DZu/ghuzdylyjaMXLymx83irM7hTxutQbD+7IhPKdMdRHD26Rm+kHRrWcrUkkRPv5pd47a2xFQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-collection": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-dismissable-layer": "1.1.0", + "@radix-ui/react-focus-guards": "1.1.0", + "@radix-ui/react-focus-scope": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-popper": "1.2.0", + "@radix-ui/react-portal": "1.1.1", + "@radix-ui/react-presence": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-roving-focus": "1.1.0", + "@radix-ui/react-slot": "1.1.0", + "@radix-ui/react-use-callback-ref": "1.1.0", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.5.7" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/react-remove-scroll": { + "version": "2.5.7", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.7.tgz", + "integrity": "sha512-FnrTWO4L7/Bhhf3CYBNArEG/yROV0tKmTv7/3h9QCFvH6sndeFf1wPqOcbFVu5VAulS5dV1wGT3GZZ/1GawqiA==", + "license": "MIT", + "dependencies": { + "react-remove-scroll-bar": "^2.3.4", + "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/@radix-ui/react-popper": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.0.tgz", @@ -3070,6 +3166,37 @@ } } }, + "node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.0.tgz", + "integrity": "sha512-EA6AMGeq9AEeQDeSH0aZgG198qkfHSbvWTf1HvoDmOB5bBG/qTxjYMWUKMnYiV6J/iP/J8MEFSuB2zRU2n7ODA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-collection": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-select": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.1.1.tgz", diff --git a/package.json b/package.json index 8646874078..07d2fb4d13 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "@nextui-org/system": "2.2.1", "@nextui-org/theme": "2.2.5", "@radix-ui/react-dialog": "^1.1.1", + "@radix-ui/react-dropdown-menu": "^2.1.1", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-label": "^2.1.0", "@radix-ui/react-select": "^2.1.1", From 0acfb6040e763d745ad8af83ad0cdde45a21eed7 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Mon, 2 Sep 2024 07:33:57 +0200 Subject: [PATCH 160/411] feat: fix TS types on auth.ts --- actions/auth.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/actions/auth.ts b/actions/auth.ts index 3c93021a12..2cdf70bfd2 100644 --- a/actions/auth.ts +++ b/actions/auth.ts @@ -1,19 +1,22 @@ "use server"; import { AuthError } from "next-auth"; +import { z } from "zod"; import { signIn, signOut } from "@/auth.config"; -// import { authFormSchema } from "@/types"; +import { authFormSchema } from "@/types"; -// const formSchema = authFormSchema("sign-in"); +const formSchemaSignIn = authFormSchema("sign-in"); -const defaultValues = { +const defaultValues: z.infer = { email: "", password: "", }; -// Fix TS types. -export async function authenticate(prevState: any, formData: any) { +export async function authenticate( + prevState: unknown, + formData: z.infer, +) { try { await new Promise((resolve) => setTimeout(resolve, 2000)); await signIn("credentials", { From 2d07186eb12ae1adc7c7993f99bc76afdf8f170e Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Mon, 2 Sep 2024 12:41:35 +0200 Subject: [PATCH 161/411] feat: integrate Zustand for global state management and apply it to the sidebar --- components/ui/sidebar/SidebarWrap.tsx | 12 +++++---- package-lock.json | 38 ++++++++++++++++++++++++++- package.json | 3 ++- 3 files changed, 46 insertions(+), 7 deletions(-) diff --git a/components/ui/sidebar/SidebarWrap.tsx b/components/ui/sidebar/SidebarWrap.tsx index e584ca1043..a917a054ad 100644 --- a/components/ui/sidebar/SidebarWrap.tsx +++ b/components/ui/sidebar/SidebarWrap.tsx @@ -10,7 +10,7 @@ import React, { useCallback } from "react"; import { useMediaQuery } from "usehooks-ts"; import { logOut } from "@/actions"; -import { useLocalStorage } from "@/hooks/useLocalStorage"; +import { useUIStore } from "@/store"; import { ProwlerExtended, @@ -25,14 +25,16 @@ export const SidebarWrap = () => { const pathname = usePathname(); const { data: session } = useSession(); - const [isCollapsed, setIsCollapsed] = useLocalStorage("isCollapsed", false); + const isCollapsed = useUIStore((state) => state.isSideMenuOpen); + const openSideMenu = useUIStore((state) => state.openSideMenu); + const closeSideMenu = useUIStore((state) => state.closeSideMenu); const isMobile = useMediaQuery("(max-width: 768px)"); - - const isCompact = Boolean(isCollapsed) || isMobile; + const isCompact = isCollapsed || isMobile; const onToggle = useCallback(() => { - setIsCollapsed(!isCollapsed); + if (!isCollapsed) openSideMenu(); + if (isCollapsed) closeSideMenu(); }, [isCollapsed]); const currentPath = pathname === "/" ? "overview" : pathname.split("/")?.[1]; diff --git a/package-lock.json b/package-lock.json index 320beae819..de4f2cd689 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,7 +43,8 @@ "tailwind-merge": "^2.5.2", "tailwindcss-animate": "^1.0.7", "uuid": "^10.0.0", - "zod": "^3.23.8" + "zod": "^3.23.8", + "zustand": "^4.5.5" }, "devDependencies": { "@iconify/react": "^5.0.1", @@ -12122,6 +12123,14 @@ } } }, + "node_modules/use-sync-external-store": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz", + "integrity": "sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/usehooks-ts": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/usehooks-ts/-/usehooks-ts-3.1.0.tgz", @@ -12416,6 +12425,33 @@ "funding": { "url": "https://github.com/sponsors/colinhacks" } + }, + "node_modules/zustand": { + "version": "4.5.5", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.5.tgz", + "integrity": "sha512-+0PALYNJNgK6hldkgDq2vLrw5f6g/jCInz52n9RTpropGgeAf/ioFUCdtsjCqu4gNhW9D01rUQBROoRjdzyn2Q==", + "dependencies": { + "use-sync-external-store": "1.2.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } } } } diff --git a/package.json b/package.json index 07d2fb4d13..8f6922e226 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,8 @@ "tailwind-merge": "^2.5.2", "tailwindcss-animate": "^1.0.7", "uuid": "^10.0.0", - "zod": "^3.23.8" + "zod": "^3.23.8", + "zustand": "^4.5.5" }, "devDependencies": { "@iconify/react": "^5.0.1", From d8ae2bf455b87b8c53beec5aaccd060b0141557c Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Mon, 2 Sep 2024 14:12:42 +0200 Subject: [PATCH 162/411] feat: integrate Zustand for global state management and apply it to the sidebar --- store/index.ts | 1 + store/ui/ui-store.ts | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 store/index.ts create mode 100644 store/ui/ui-store.ts diff --git a/store/index.ts b/store/index.ts new file mode 100644 index 0000000000..36c2042a88 --- /dev/null +++ b/store/index.ts @@ -0,0 +1 @@ +export * from "./ui/ui-store"; diff --git a/store/ui/ui-store.ts b/store/ui/ui-store.ts new file mode 100644 index 0000000000..b47d474c94 --- /dev/null +++ b/store/ui/ui-store.ts @@ -0,0 +1,22 @@ +import { create } from "zustand"; +import { persist } from "zustand/middleware"; + +interface SidebarStoreState { + isSideMenuOpen: boolean; + + openSideMenu: () => void; + closeSideMenu: () => void; +} + +export const useUIStore = create()( + persist( + (set) => ({ + isSideMenuOpen: true, + openSideMenu: () => set({ isSideMenuOpen: true }), + closeSideMenu: () => set({ isSideMenuOpen: false }), + }), + { + name: "sidebar-store", + }, + ), +); From 45f398bf309f2d00c086b674650da089e3f0ef89 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Tue, 3 Sep 2024 16:31:05 +0200 Subject: [PATCH 163/411] chore: add sorting to provider's table --- actions/providers.ts | 28 +++++--- app/(prowler)/providers/page.tsx | 31 +++++++-- auth.config.ts | 1 + .../providers/table/ColumnsProvider.tsx | 21 +++++- .../providers/table/DataTableColumnHeader.tsx | 69 ++++++++++++++----- lib/seed.ts | 2 + nextauth.d.ts | 1 + 7 files changed, 120 insertions(+), 33 deletions(-) diff --git a/actions/providers.ts b/actions/providers.ts index 6892e8c922..8c50440f46 100644 --- a/actions/providers.ts +++ b/actions/providers.ts @@ -3,22 +3,33 @@ import { revalidatePath } from "next/cache"; import { redirect } from "next/navigation"; +import { auth } from "@/auth.config"; import { parseStringify } from "@/lib"; -export const getProvider = async ({ page = 1 }) => { +export const getProvider = async ({ + page = 1, + query = "", + sort = "", + filter = "", +}) => { + const session = await auth(); + const tenantId = session?.user.tenantId; + if (isNaN(Number(page)) || page < 1) redirect("/providers"); const keyServer = process.env.LOCAL_SERVER_URL; + const url = new URL(`${keyServer}/providers`); + if (page) url.searchParams.append("page[number]", page.toString()); + if (query) url.searchParams.append("filter[query]", query); + if (sort) url.searchParams.append("sort", sort); + if (filter) url.searchParams.append("filter[provider]", filter); try { - const providers = await fetch( - `${keyServer}/providers?page%5Bnumber%5D=${page}`, - { - headers: { - "X-Tenant-ID": `${process.env.HEADER_TENANT_ID}`, - }, + const providers = await fetch(`${url.toString()}`, { + headers: { + "X-Tenant-ID": `${tenantId}`, }, - ); + }); const data = await providers.json(); const parsedData = parseStringify(data); revalidatePath("/providers"); @@ -28,6 +39,7 @@ export const getProvider = async ({ page = 1 }) => { return undefined; } }; + export const addProvider = async (formData: FormData) => { const keyServer = process.env.LOCAL_SERVER_URL; diff --git a/app/(prowler)/providers/page.tsx b/app/(prowler)/providers/page.tsx index 780d736710..c49117fcd2 100644 --- a/app/(prowler)/providers/page.tsx +++ b/app/(prowler)/providers/page.tsx @@ -10,9 +10,15 @@ import { SkeletonTableProvider, } from "@/components/providers"; import { Header } from "@/components/ui"; -import { searchParamsProps } from "@/types"; -export default async function Providers({ searchParams }: searchParamsProps) { +export default async function Providers({ + searchParams, +}: { + searchParams?: { + query?: string; + page: string; + }; +}) { return ( <>
@@ -22,7 +28,7 @@ export default async function Providers({ searchParams }: searchParamsProps) {
- }> + }>
@@ -30,9 +36,22 @@ export default async function Providers({ searchParams }: searchParamsProps) { ); } -const SSRDataTable = async ({ searchParams }: searchParamsProps) => { - const page = searchParams.page ? parseInt(searchParams.page) : 1; - const providersData = await getProvider({ page }); +const SSRDataTable = async ({ + searchParams, +}: { + searchParams?: { + query?: string; + page?: string; + sort?: string; + filter?: string; + }; +}) => { + const query = searchParams?.query || ""; + const page = searchParams?.page ? parseInt(searchParams.page) : 1; + const sort = searchParams?.sort || ""; + const filter = searchParams?.filter || ""; + + const providersData = await getProvider({ query, page, sort, filter }); const [providers] = await Promise.all([providersData]); if (providers?.errors) redirect("/providers"); diff --git a/auth.config.ts b/auth.config.ts index 93d8387346..55a81edcca 100644 --- a/auth.config.ts +++ b/auth.config.ts @@ -14,6 +14,7 @@ async function getUser(email: string, password: string): Promise { return { id: user.id, + tenantId: user.tenantId, name: user.name, companyName: user.companyName, email: user.email, diff --git a/components/providers/table/ColumnsProvider.tsx b/components/providers/table/ColumnsProvider.tsx index 82fe4992e2..7a6b6d6270 100644 --- a/components/providers/table/ColumnsProvider.tsx +++ b/components/providers/table/ColumnsProvider.tsx @@ -18,6 +18,7 @@ import { CheckConnectionProvider } from "../CheckConnectionProvider"; import { DateWithTime } from "../DateWithTime"; import { DeleteProvider } from "../DeleteProvider"; import { ProviderInfo } from "../ProviderInfo"; +import { DataTableColumnHeader } from "./DataTableColumnHeader"; const getProviderData = (row: { original: ProviderProps }) => { return row.original; @@ -30,7 +31,9 @@ export const ColumnsProvider: ColumnDef[] = [ }, { accessorKey: "account", - header: "Account", + header: ({ column }) => ( + + ), cell: ({ row }) => { const { attributes: { connection, provider, alias, provider_id }, @@ -55,7 +58,13 @@ export const ColumnsProvider: ColumnDef[] = [ }, { accessorKey: "lastScan", - header: "Last Scan", + header: ({ column }) => ( + + ), cell: ({ row }) => { const { attributes: { updated_at }, @@ -86,7 +95,13 @@ export const ColumnsProvider: ColumnDef[] = [ }, { accessorKey: "added", - header: "Added", + header: ({ column }) => ( + + ), cell: ({ row }) => { const { attributes: { inserted_at }, diff --git a/components/providers/table/DataTableColumnHeader.tsx b/components/providers/table/DataTableColumnHeader.tsx index 7fe6027527..6d460c5fb0 100644 --- a/components/providers/table/DataTableColumnHeader.tsx +++ b/components/providers/table/DataTableColumnHeader.tsx @@ -1,3 +1,5 @@ +"use client"; + import { Button } from "@nextui-org/react"; import { Column } from "@tanstack/react-table"; import { @@ -5,25 +7,61 @@ import { ArrowUpIcon, ChevronsLeftRightIcon, } from "lucide-react"; +import { usePathname, useRouter, useSearchParams } from "next/navigation"; import { HTMLAttributes } from "react"; interface DataTableColumnHeaderProps extends HTMLAttributes { column: Column; title: string; + param?: string; } export const DataTableColumnHeader = ({ column, title, - className, + param, }: DataTableColumnHeaderProps) => { + const router = useRouter(); + const pathname = usePathname(); + const searchParams = useSearchParams(); + + const getToggleSortingHandler = () => { + const currentSortParam = searchParams.get("sort"); + let newSortParam = ""; + + if ( + !currentSortParam || + currentSortParam === "" || + currentSortParam !== param + ) { + // Sort ascending for the first time or switch to a different column + newSortParam = `${param}`; + } else if (currentSortParam === param) { + // If already sorting ascending, switch to descending + newSortParam = `-${param}`; + } else if (currentSortParam === `-${param}`) { + // If already sorting descending, remove sorting + newSortParam = ""; + } + + // Construct the new URL with the sorting parameter + const newUrl = newSortParam ? `${pathname}?sort=${newSortParam}` : pathname; + + router.push(newUrl, { + scroll: false, + }); + }; const renderSortIcon = () => { - const sort = column.getIsSorted(); - if (!sort) { + const currentSortParam = searchParams.get("sort"); + if ( + !currentSortParam || + currentSortParam === "" || + (currentSortParam !== param && currentSortParam !== `-${param}`) + ) { return ; } - return sort === "desc" ? ( + return currentSortParam === `-${param}` ? ( ) : ( @@ -31,19 +69,18 @@ export const DataTableColumnHeader = ({ }; if (!column.getCanSort()) { - return
{title}
; + return
{title}
; } + return ( -
- -
+ ); }; diff --git a/lib/seed.ts b/lib/seed.ts index 4a808d2219..1ff00ee60b 100644 --- a/lib/seed.ts +++ b/lib/seed.ts @@ -4,6 +4,7 @@ import { v4 as uuidv4 } from "uuid"; export const userMockData = [ { id: uuidv4(), // Generate a unique UUID. + tenantId: "12646005-9067-4d2a-a098-8bb378604362", email: "admin@prowler.com", name: "Admin Prowler", companyName: "Prowler", @@ -13,6 +14,7 @@ export const userMockData = [ }, { id: uuidv4(), // Generate a unique UUID. + tenantId: "12646005-9067-4d2a-a098-8bb378604362", email: "user@prowler.com", name: "User Prowler", companyName: "Prowler", diff --git a/nextauth.d.ts b/nextauth.d.ts index 052e4d4a7c..2fe2480ce5 100644 --- a/nextauth.d.ts +++ b/nextauth.d.ts @@ -4,6 +4,7 @@ declare module "next-auth" { interface Session { user: { id: string; + tenantId: string; firstName: string; companyName: string; email: string; From a1021fbca7866964a928165cad6bbf790862f0ff Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Wed, 4 Sep 2024 09:00:18 +0200 Subject: [PATCH 164/411] chore: improve sorting --- app/(prowler)/providers/page.tsx | 8 ++++++-- .../providers/table/DataTableColumnHeader.tsx | 14 +++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/app/(prowler)/providers/page.tsx b/app/(prowler)/providers/page.tsx index c49117fcd2..c6d380ccac 100644 --- a/app/(prowler)/providers/page.tsx +++ b/app/(prowler)/providers/page.tsx @@ -16,9 +16,13 @@ export default async function Providers({ }: { searchParams?: { query?: string; - page: string; + page?: string; + sort?: string; + filter?: string; }; }) { + const searchParamsKey = JSON.stringify(searchParams || {}); + return ( <>
@@ -28,7 +32,7 @@ export default async function Providers({
- }> + }>
diff --git a/components/providers/table/DataTableColumnHeader.tsx b/components/providers/table/DataTableColumnHeader.tsx index 6d460c5fb0..6051a3ef87 100644 --- a/components/providers/table/DataTableColumnHeader.tsx +++ b/components/providers/table/DataTableColumnHeader.tsx @@ -30,19 +30,15 @@ export const DataTableColumnHeader = ({ const currentSortParam = searchParams.get("sort"); let newSortParam = ""; - if ( - !currentSortParam || - currentSortParam === "" || - currentSortParam !== param - ) { - // Sort ascending for the first time or switch to a different column - newSortParam = `${param}`; - } else if (currentSortParam === param) { + if (currentSortParam === `${param}`) { // If already sorting ascending, switch to descending newSortParam = `-${param}`; } else if (currentSortParam === `-${param}`) { // If already sorting descending, remove sorting newSortParam = ""; + } else { + // Sort ascending for the first time or switch to a different column + newSortParam = `${param}`; } // Construct the new URL with the sorting parameter @@ -76,7 +72,7 @@ export const DataTableColumnHeader = ({
); }; diff --git a/components/findings/table/index.ts b/components/findings/table/index.ts index 3aad78e3f4..fe19b5dd6e 100644 --- a/components/findings/table/index.ts +++ b/components/findings/table/index.ts @@ -1,5 +1,5 @@ export * from "./column-findings"; export * from "./data-table-row-actions"; export * from "./data-table-row-details"; -export * from "./skeleton-table-findings"; export * from "./finding-detail"; +export * from "./skeleton-table-findings"; diff --git a/components/scans/table/scans/column-get-scans.tsx b/components/scans/table/scans/column-get-scans.tsx index fccc203ae6..0d8bfb4360 100644 --- a/components/scans/table/scans/column-get-scans.tsx +++ b/components/scans/table/scans/column-get-scans.tsx @@ -45,17 +45,16 @@ export const ColumnGetScans: ColumnDef[] = [ ); }, }, - - { - accessorKey: "scanner_args", - header: "Scanner Args", - cell: ({ row }) => { - const { - attributes: { scanner_args }, - } = getScanData(row); - return

{scanner_args?.only_logs}

; - }, - }, + // { + // accessorKey: "scanner_args", + // header: "Scanner Args", + // cell: ({ row }) => { + // const { + // attributes: { scanner_args }, + // } = getScanData(row); + // return

{scanner_args?.only_logs}

; + // }, + // }, { accessorKey: "resources", header: "Resources", diff --git a/package.json b/package.json index 2d9f0f0ba4..765c868597 100644 --- a/package.json +++ b/package.json @@ -81,7 +81,7 @@ "build": "next build", "start": "next start", "typecheck": "tsc", - "healthcheck": "npm run typecheck && npm run lint:check", + "healthcheck": "npm run typecheck && npm run lint:check && npm run build", "lint:check": "./node_modules/.bin/eslint ./app", "lint:fix": "eslint . --ext .ts,.tsx -c .eslintrc.cjs --fix", "format:check": "./node_modules/.bin/prettier --check ./app", From 8fb6f5b11d222501fb205298f1f2088223894c49 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Wed, 13 Nov 2024 08:22:58 +0100 Subject: [PATCH 371/411] chore: add GitHub action to run the build --- .github/workflows/checks.yml | 2 ++ package.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 9dfa42e70c..e58e18c793 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -25,3 +25,5 @@ jobs: run: npm install - name: Run Healthcheck run: npm run healthcheck + - name: Build the application + run: npm run build diff --git a/package.json b/package.json index 765c868597..2d9f0f0ba4 100644 --- a/package.json +++ b/package.json @@ -81,7 +81,7 @@ "build": "next build", "start": "next start", "typecheck": "tsc", - "healthcheck": "npm run typecheck && npm run lint:check && npm run build", + "healthcheck": "npm run typecheck && npm run lint:check", "lint:check": "./node_modules/.bin/eslint ./app", "lint:fix": "eslint . --ext .ts,.tsx -c .eslintrc.cjs --fix", "format:check": "./node_modules/.bin/prettier --check ./app", From 4603e6b46d3d5a6c10ca6753145196504d7c0ff3 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Wed, 13 Nov 2024 10:07:14 +0100 Subject: [PATCH 372/411] chore: invert severity filter list order --- components/filters/data-filters.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/filters/data-filters.ts b/components/filters/data-filters.ts index 673e4f0fff..97695d2970 100644 --- a/components/filters/data-filters.ts +++ b/components/filters/data-filters.ts @@ -37,7 +37,7 @@ export const filterFindings = [ { key: "severity", labelCheckboxGroup: "Severity", - values: ["informational", "low", "medium", "high", "critical"], + values: ["critical", "high", "medium", "low", "informational"], }, { key: "status", From 239826ce1f9d0b723c991d14fffebdaf4510f3a3 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Wed, 13 Nov 2024 14:24:44 +0100 Subject: [PATCH 373/411] chore: remove old files and add new ones related to users --- components/users/table/ColumnsUser.tsx | 56 --------- .../users/table/DataTableColumnHeader.tsx | 49 -------- .../users/table/DataTablePagination.tsx | 84 ------------- components/users/table/DataTableUser.tsx | 107 ----------------- components/users/table/UserActions.tsx | 54 --------- components/users/table/column-users.tsx | 74 ++++++++++++ .../users/table/data-table-row-actions.tsx | 110 ++++++++++++++++++ components/users/table/index.ts | 3 + ...nTableUser.tsx => skeleton-table-user.tsx} | 0 9 files changed, 187 insertions(+), 350 deletions(-) delete mode 100644 components/users/table/ColumnsUser.tsx delete mode 100644 components/users/table/DataTableColumnHeader.tsx delete mode 100644 components/users/table/DataTablePagination.tsx delete mode 100644 components/users/table/DataTableUser.tsx delete mode 100644 components/users/table/UserActions.tsx create mode 100644 components/users/table/column-users.tsx create mode 100644 components/users/table/data-table-row-actions.tsx create mode 100644 components/users/table/index.ts rename components/users/table/{SkeletonTableUser.tsx => skeleton-table-user.tsx} (100%) diff --git a/components/users/table/ColumnsUser.tsx b/components/users/table/ColumnsUser.tsx deleted file mode 100644 index 20ac2dba9d..0000000000 --- a/components/users/table/ColumnsUser.tsx +++ /dev/null @@ -1,56 +0,0 @@ -"use client"; - -import { ColumnDef } from "@tanstack/react-table"; - -import { DateWithTime } from "@/components/ui/entities"; -import { UserActions } from "@/components/users"; -import { UserProps } from "@/types"; - -const getUserData = (row: { original: UserProps }) => { - return row.original; -}; - -export const ColumnsUser: ColumnDef[] = [ - { - accessorKey: "email", - header: "Email", - cell: ({ row }) => { - const { email } = getUserData(row); - return

{email}

; - }, - }, - { - accessorKey: "name", - header: "Name", - cell: ({ row }) => { - const { name } = getUserData(row); - return

{name}

; - }, - }, - { - accessorKey: "role", - header: "Role", - cell: ({ row }) => { - const { role } = getUserData(row); - return

{role}

; - }, - }, - { - accessorKey: "added", - header: "Added", - cell: ({ row }) => { - const { dateAdded } = getUserData(row); - return ; - }, - }, - - { - accessorKey: "actions", - header: () =>
Actions
, - id: "actions", - cell: ({ row }) => { - const userData = getUserData(row); - return ; - }, - }, -]; diff --git a/components/users/table/DataTableColumnHeader.tsx b/components/users/table/DataTableColumnHeader.tsx deleted file mode 100644 index 7fe6027527..0000000000 --- a/components/users/table/DataTableColumnHeader.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { Button } from "@nextui-org/react"; -import { Column } from "@tanstack/react-table"; -import { - ArrowDownIcon, - ArrowUpIcon, - ChevronsLeftRightIcon, -} from "lucide-react"; -import { HTMLAttributes } from "react"; - -interface DataTableColumnHeaderProps - extends HTMLAttributes { - column: Column; - title: string; -} - -export const DataTableColumnHeader = ({ - column, - title, - className, -}: DataTableColumnHeaderProps) => { - const renderSortIcon = () => { - const sort = column.getIsSorted(); - if (!sort) { - return ; - } - return sort === "desc" ? ( - - ) : ( - - ); - }; - - if (!column.getCanSort()) { - return
{title}
; - } - return ( -
- -
- ); -}; diff --git a/components/users/table/DataTablePagination.tsx b/components/users/table/DataTablePagination.tsx deleted file mode 100644 index e17170d7c8..0000000000 --- a/components/users/table/DataTablePagination.tsx +++ /dev/null @@ -1,84 +0,0 @@ -"use client"; - -import { - ChevronLeftIcon, - ChevronRightIcon, - DoubleArrowLeftIcon, - DoubleArrowRightIcon, -} from "@radix-ui/react-icons"; -import Link from "next/link"; -import { usePathname, useSearchParams } from "next/navigation"; - -import { getPaginationInfo } from "@/lib"; -import { MetaDataProps } from "@/types"; - -interface DataTablePaginationProps { - pageSizeOptions?: number[]; - metadata?: MetaDataProps; -} - -export function DataTablePagination({ metadata }: DataTablePaginationProps) { - if (!metadata) return null; - const pathname = usePathname(); - const searchParams = useSearchParams(); - - const { currentPage, totalPages, totalEntries } = getPaginationInfo(metadata); - - const createPageUrl = (pageNumber: number | string) => { - const params = new URLSearchParams(searchParams); - - if (pageNumber === "...") return `${pathname}?${params.toString()}`; - - if (+pageNumber > totalPages) { - return `${pathname}?${params.toString()}`; - } - - params.set("page", pageNumber.toString()); - return `${pathname}?${params.toString()}`; - }; - - return ( -
-
- {totalEntries} entries in Total. -
-
-
- Page {currentPage} of {totalPages} -
-
- -
-
-
- ); -} diff --git a/components/users/table/DataTableUser.tsx b/components/users/table/DataTableUser.tsx deleted file mode 100644 index 0211491262..0000000000 --- a/components/users/table/DataTableUser.tsx +++ /dev/null @@ -1,107 +0,0 @@ -"use client"; - -import { - ColumnDef, - flexRender, - getCoreRowModel, - getPaginationRowModel, - getSortedRowModel, - SortingState, - useReactTable, -} from "@tanstack/react-table"; -import { useState } from "react"; - -import { - Table, - TableBody, - TableCell, - TableHead, - TableHeader, - TableRow, -} from "@/components/ui/table"; -import { MetaDataProps } from "@/types"; - -import { DataTablePagination } from "./DataTablePagination"; - -interface DataTableUserProps { - columns: ColumnDef[]; - data: TData[]; - metadata?: MetaDataProps; -} - -export function DataTableUser({ - columns, - data, - metadata, -}: DataTableUserProps) { - const [sorting, setSorting] = useState([]); - const table = useReactTable({ - data, - columns, - enableSorting: true, - getCoreRowModel: getCoreRowModel(), - getPaginationRowModel: getPaginationRowModel(), - onSortingChange: setSorting, - getSortedRowModel: getSortedRowModel(), - state: { - sorting, - }, - }); - return ( - <> -
- - - {table.getHeaderGroups().map((headerGroup) => ( - - {headerGroup.headers.map((header) => { - return ( - - {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.header, - header.getContext(), - )} - - ); - })} - - ))} - - - {table.getRowModel().rows?.length ? ( - table.getRowModel().rows.map((row) => ( - - {row.getVisibleCells().map((cell) => ( - - {flexRender( - cell.column.columnDef.cell, - cell.getContext(), - )} - - ))} - - )) - ) : ( - - - No results. - - - )} - -
-
-
- -
- - ); -} diff --git a/components/users/table/UserActions.tsx b/components/users/table/UserActions.tsx deleted file mode 100644 index 15d0e3e67a..0000000000 --- a/components/users/table/UserActions.tsx +++ /dev/null @@ -1,54 +0,0 @@ -"use client"; - -import { useState } from "react"; - -import { VerticalDotsIcon } from "@/components/icons"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from "@/components/ui"; -import { EditUserModal } from "@/components/users"; -import { UserProps } from "@/types"; - -interface UserActionsProps { - userData: UserProps; -} - -export const UserActions: React.FC = ({ userData }) => { - const [isEditOpen, setIsEditOpen] = useState(false); - - return ( - <> -
- - - - - - setIsEditOpen(true)} - > - Edit - - - Delete - - - -
- {isEditOpen && ( - - )} - - ); -}; diff --git a/components/users/table/column-users.tsx b/components/users/table/column-users.tsx new file mode 100644 index 0000000000..084982a659 --- /dev/null +++ b/components/users/table/column-users.tsx @@ -0,0 +1,74 @@ +"use client"; + +import { ColumnDef } from "@tanstack/react-table"; + +import { DateWithTime } from "@/components/ui/entities"; +import { DataTableColumnHeader } from "@/components/ui/table"; +import { UserProps } from "@/types"; + +import { DataTableRowActions } from "./data-table-row-actions"; + +const getUserData = (row: { original: UserProps }) => { + return row.original.attributes; +}; + +export const ColumnsUser: ColumnDef[] = [ + { + accessorKey: "name", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + const data = getUserData(row); + return

{data?.name || "N/A"}

; + }, + }, + { + accessorKey: "email", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + const { email } = getUserData(row); + return

{email}

; + }, + }, + { + accessorKey: "company_name", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + const { company_name } = getUserData(row); + return

{company_name}

; + }, + }, + + { + accessorKey: "date_joined", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + const { date_joined } = getUserData(row); + return ; + }, + }, + + { + accessorKey: "actions", + header: () =>
Actions
, + id: "actions", + cell: ({ row }) => { + return ; + }, + }, +]; diff --git a/components/users/table/data-table-row-actions.tsx b/components/users/table/data-table-row-actions.tsx new file mode 100644 index 0000000000..79825bf168 --- /dev/null +++ b/components/users/table/data-table-row-actions.tsx @@ -0,0 +1,110 @@ +"use client"; + +import { + Button, + Dropdown, + DropdownItem, + DropdownMenu, + DropdownSection, + DropdownTrigger, +} from "@nextui-org/react"; +import { + DeleteDocumentBulkIcon, + EditDocumentBulkIcon, +} from "@nextui-org/shared-icons"; +import { Row } from "@tanstack/react-table"; +import clsx from "clsx"; +import { useState } from "react"; + +import { VerticalDotsIcon } from "@/components/icons"; +import { CustomAlertModal } from "@/components/ui/custom"; + +import { DeleteForm, EditForm } from "../forms"; + +interface DataTableRowActionsProps { + row: Row; +} +const iconClasses = + "text-2xl text-default-500 pointer-events-none flex-shrink-0"; + +export function DataTableRowActions({ + row, +}: DataTableRowActionsProps) { + const [isEditOpen, setIsEditOpen] = useState(false); + const [isDeleteOpen, setIsDeleteOpen] = useState(false); + const userId = (row.original as { id: string }).id; + const userName = (row.original as any).attributes?.name; + const userEmail = (row.original as any).attributes?.email; + const userCompanyName = (row.original as any).attributes?.company_name; + return ( + <> + + + + + + + +
+ + + + + + + } + // onClick={() => setIsEditOpen(true)} + > + Edit User + + + + + } + onClick={() => setIsDeleteOpen(true)} + > + Delete User + + + + +
+ + ); +} diff --git a/components/users/table/index.ts b/components/users/table/index.ts new file mode 100644 index 0000000000..1a3b6efabe --- /dev/null +++ b/components/users/table/index.ts @@ -0,0 +1,3 @@ +export * from "./column-users"; +export * from "./data-table-row-actions"; +export * from "./skeleton-table-user"; diff --git a/components/users/table/SkeletonTableUser.tsx b/components/users/table/skeleton-table-user.tsx similarity index 100% rename from components/users/table/SkeletonTableUser.tsx rename to components/users/table/skeleton-table-user.tsx From 833bf0520c22a7c8bbecb90f50ea1f24e6416f5d Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Wed, 13 Nov 2024 14:25:31 +0100 Subject: [PATCH 374/411] chore: remove old files and add new ones related to users --- components/filters/data-filters.ts | 8 ++ components/users/AddUserModal.tsx | 62 ---------- components/users/ButtonAddUser.tsx | 14 --- components/users/ButtonEditUser.tsx | 14 --- components/users/CustomSelectUser.tsx | 39 ------- components/users/EditUserModal.tsx | 82 ------------- components/users/forms/delete-form.tsx | 97 ++++++++++++++++ components/users/forms/edit-form.tsx | 155 +++++++++++++++++++++++++ components/users/forms/index.ts | 2 + components/users/index.ts | 11 -- dataUsers.json | 98 ---------------- 11 files changed, 262 insertions(+), 320 deletions(-) delete mode 100644 components/users/AddUserModal.tsx delete mode 100644 components/users/ButtonAddUser.tsx delete mode 100644 components/users/ButtonEditUser.tsx delete mode 100644 components/users/CustomSelectUser.tsx delete mode 100644 components/users/EditUserModal.tsx create mode 100644 components/users/forms/delete-form.tsx create mode 100644 components/users/forms/edit-form.tsx create mode 100644 components/users/forms/index.ts delete mode 100644 components/users/index.ts delete mode 100644 dataUsers.json diff --git a/components/filters/data-filters.ts b/components/filters/data-filters.ts index 97695d2970..21691b2c5c 100644 --- a/components/filters/data-filters.ts +++ b/components/filters/data-filters.ts @@ -51,3 +51,11 @@ export const filterFindings = [ }, // Add more filter categories as needed ]; + +export const filterUsers = [ + { + key: "is_active", + labelCheckboxGroup: "Status", + values: ["true", "false"], + }, +]; diff --git a/components/users/AddUserModal.tsx b/components/users/AddUserModal.tsx deleted file mode 100644 index e309ea0516..0000000000 --- a/components/users/AddUserModal.tsx +++ /dev/null @@ -1,62 +0,0 @@ -"use client"; - -import { Button, Input } from "@nextui-org/react"; -import { useRef, useState } from "react"; - -import { - Dialog, - DialogContent, - DialogHeader, - DialogTitle, - DialogTrigger, -} from "@/components/ui"; - -import { ButtonAddUser } from "./ButtonAddUser"; -import { CustomSelectUser } from "./CustomSelectUser"; - -export const AddUserModal = () => { - const [open, setOpen] = useState(false); - const ref = useRef(null); - - return ( - - - - - - - Add User - -
setOpen(false)}> -
- - -
-
- -
-
-
-
- ); -}; diff --git a/components/users/ButtonAddUser.tsx b/components/users/ButtonAddUser.tsx deleted file mode 100644 index e840dc0157..0000000000 --- a/components/users/ButtonAddUser.tsx +++ /dev/null @@ -1,14 +0,0 @@ -"use client"; - -import { Button } from "@nextui-org/react"; -import React from "react"; -import { useFormStatus } from "react-dom"; - -export const ButtonAddUser = () => { - const { pending } = useFormStatus(); - return ( - - ); -}; diff --git a/components/users/ButtonEditUser.tsx b/components/users/ButtonEditUser.tsx deleted file mode 100644 index a0322b7a2d..0000000000 --- a/components/users/ButtonEditUser.tsx +++ /dev/null @@ -1,14 +0,0 @@ -"use client"; - -import { Button } from "@nextui-org/react"; -import React from "react"; -import { useFormStatus } from "react-dom"; - -export const ButtonEditUser = () => { - const { pending } = useFormStatus(); - return ( - - ); -}; diff --git a/components/users/CustomSelectUser.tsx b/components/users/CustomSelectUser.tsx deleted file mode 100644 index 676fe881e6..0000000000 --- a/components/users/CustomSelectUser.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { - Label, - Select, - SelectContent, - SelectGroup, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui"; -import { UserProps } from "@/types"; - -interface CustomSelectUserProps { - userData?: UserProps; -} - -export const CustomSelectUser: React.FC = ({ - userData, -}) => { - return ( - <> - - - - ); -}; diff --git a/components/users/EditUserModal.tsx b/components/users/EditUserModal.tsx deleted file mode 100644 index 45d7c86791..0000000000 --- a/components/users/EditUserModal.tsx +++ /dev/null @@ -1,82 +0,0 @@ -"use client"; - -import { Input } from "@nextui-org/react"; -import { useRef } from "react"; - -import { - Dialog, - DialogContent, - DialogHeader, - DialogTitle, -} from "@/components/ui"; -import { UserProps } from "@/types"; - -import { ButtonEditUser } from "./ButtonEditUser"; -import { CustomSelectUser } from "./CustomSelectUser"; - -interface EditUserModalProps { - isOpen: boolean; - setIsOpen: React.Dispatch>; - userData: UserProps; -} - -export const EditUserModal: React.FC = ({ - isOpen, - setIsOpen, - userData, -}) => { - const ref = useRef(null); - - return ( - - - - Edit User - -
setIsOpen(false)}> -
- - - -
-
- -
-
-
-
- ); -}; diff --git a/components/users/forms/delete-form.tsx b/components/users/forms/delete-form.tsx new file mode 100644 index 0000000000..36843534b9 --- /dev/null +++ b/components/users/forms/delete-form.tsx @@ -0,0 +1,97 @@ +"use client"; + +import { zodResolver } from "@hookform/resolvers/zod"; +import React, { Dispatch, SetStateAction } from "react"; +import { useForm } from "react-hook-form"; +import * as z from "zod"; + +import { deleteUser } from "@/actions/users/users"; +import { DeleteIcon } from "@/components/icons"; +import { useToast } from "@/components/ui"; +import { CustomButton } from "@/components/ui/custom"; +import { Form } from "@/components/ui/form"; + +const formSchema = z.object({ + userId: z.string(), +}); + +export const DeleteForm = ({ + userId, + setIsOpen, +}: { + userId: string; + setIsOpen: Dispatch>; +}) => { + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + userId, + }, + }); + const { toast } = useToast(); + const isLoading = form.formState.isSubmitting; + + async function onSubmitClient(values: z.infer) { + const formData = new FormData(); + + Object.entries(values).forEach( + ([key, value]) => value !== undefined && formData.append(key, value), + ); + + console.log(formData); + // client-side validation + const data = await deleteUser(formData); + + if (data?.errors && data.errors.length > 0) { + const error = data.errors[0]; + const errorMessage = `${error.detail}`; + // show error + toast({ + variant: "destructive", + title: "Oops! Something went wrong", + description: errorMessage, + }); + } else { + toast({ + title: "Success!", + description: "The user was removed successfully.", + }); + } + setIsOpen(false); // Close the modal on success + } + + return ( +
+ + +
+ setIsOpen(false)} + isDisabled={isLoading} + > + Cancel + + + } + > + {isLoading ? <>Loading : Delete} + +
+
+ + ); +}; diff --git a/components/users/forms/edit-form.tsx b/components/users/forms/edit-form.tsx new file mode 100644 index 0000000000..ece7080b5a --- /dev/null +++ b/components/users/forms/edit-form.tsx @@ -0,0 +1,155 @@ +"use client"; + +import { zodResolver } from "@hookform/resolvers/zod"; +import { Dispatch, SetStateAction } from "react"; +import { useForm } from "react-hook-form"; +import * as z from "zod"; + +import { updateUser } from "@/actions/users/users"; +import { SaveIcon } from "@/components/icons"; +import { useToast } from "@/components/ui"; +import { CustomButton, CustomInput } from "@/components/ui/custom"; +import { Form } from "@/components/ui/form"; +import { editUserFormSchema } from "@/types"; + +export const EditForm = ({ + userId, + userName, + userEmail, + userCompanyName, + setIsOpen, +}: { + userId: string; + userName?: string; + userEmail?: string; + userCompanyName?: string; + setIsOpen: Dispatch>; +}) => { + const formSchema = editUserFormSchema( + userName ?? "", + userEmail ?? "", + userCompanyName ?? "", + ); + + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + userId: userId, + name: userName, + email: userEmail, + company_name: userCompanyName, + }, + }); + + const { toast } = useToast(); + + const isLoading = form.formState.isSubmitting; + + const onSubmitClient = async (values: z.infer) => { + const formData = new FormData(); + + Object.entries(values).forEach( + ([key, value]) => value !== undefined && formData.append(key, value), + ); + + const data = await updateUser(formData); + + if (data?.errors && data.errors.length > 0) { + const error = data.errors[0]; + const errorMessage = `${error.detail}`; + // show error + toast({ + variant: "destructive", + title: "Oops! Something went wrong", + description: errorMessage, + }); + } else { + toast({ + title: "Success!", + description: "The user was updated successfully.", + }); + setIsOpen(false); // Close the modal on success + } + }; + + return ( +
+ +
+ Current name: {userName} +
+
+ +
+
+ +
+
+ +
+ + +
+ setIsOpen(false)} + isDisabled={isLoading} + > + Cancel + + + } + > + {isLoading ? <>Loading : Save} + +
+
+ + ); +}; diff --git a/components/users/forms/index.ts b/components/users/forms/index.ts new file mode 100644 index 0000000000..a081952ade --- /dev/null +++ b/components/users/forms/index.ts @@ -0,0 +1,2 @@ +export * from "./delete-form"; +export * from "./edit-form"; diff --git a/components/users/index.ts b/components/users/index.ts deleted file mode 100644 index 36f6d8f557..0000000000 --- a/components/users/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -export * from "./AddUserModal"; -export * from "./ButtonAddUser"; -export * from "./ButtonEditUser"; -export * from "./CustomSelectUser"; -export * from "./EditUserModal"; -export * from "./table/ColumnsUser"; -export * from "./table/DataTableColumnHeader"; -export * from "./table/DataTablePagination"; -export * from "./table/DataTableUser"; -export * from "./table/SkeletonTableUser"; -export * from "./table/UserActions"; diff --git a/dataUsers.json b/dataUsers.json deleted file mode 100644 index 26428a633a..0000000000 --- a/dataUsers.json +++ /dev/null @@ -1,98 +0,0 @@ -{ - "links": { - "first": "http://localhost:8080/api/v1/users?page%5Bnumber%5D=1", - "last": "http://localhost:8080/api/v1/users?page%5Bnumber%5D=1", - "next": null, - "prev": null - }, - "data": [ - { - "id": "001", - "email": "john.doe@example.com", - "name": "John Doe", - "role": "Admin", - "dateAdded": "2024-08-14T10:00:00.000000Z", - "status": "active" - }, - { - "id": "002", - "email": "jane.smith@example.com", - "name": "Jane Smith", - "role": "User", - "dateAdded": "2024-08-15T11:30:00.000000Z", - "status": "inactive" - }, - { - "id": "003", - "email": "will.johnson@example.com", - "name": "Will Johnson", - "role": "Admin", - "dateAdded": "2024-08-16T09:15:00.000000Z", - "status": "active" - }, - { - "id": "004", - "email": "emily.davis@example.com", - "name": "Emily Davis", - "role": "User", - "dateAdded": "2024-08-17T14:00:00.000000Z", - "status": "active" - }, - { - "id": "005", - "email": "michael.brown@example.com", - "name": "Michael Brown", - "role": "User", - "dateAdded": "2024-08-18T08:45:00.000000Z", - "status": "inactive" - }, - { - "id": "006", - "email": "sarah.miller@example.com", - "name": "Sarah Miller", - "role": "Admin", - "dateAdded": "2024-08-19T13:25:00.000000Z", - "status": "active" - }, - { - "id": "007", - "email": "david.wilson@example.com", - "name": "David Wilson", - "role": "User", - "dateAdded": "2024-08-20T10:50:00.000000Z", - "status": "active" - }, - { - "id": "008", - "email": "lisa.moore@example.com", - "name": "Lisa Moore", - "role": "Admin", - "dateAdded": "2024-08-21T07:30:00.000000Z", - "status": "inactive" - }, - { - "id": "009", - "email": "james.taylor@example.com", - "name": "James Taylor", - "role": "User", - "dateAdded": "2024-08-22T12:10:00.000000Z", - "status": "active" - }, - { - "id": "010", - "email": "anna.anderson@example.com", - "name": "Anna Anderson", - "role": "User", - "dateAdded": "2024-08-23T11:00:00.000000Z", - "status": "inactive" - } - ], - "meta": { - "pagination": { - "page": 1, - "pages": 1, - "count": 10 - }, - "version": "v1" - } -} From 0290b837f20f9c5447d893cafa3af16070041d32 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Wed, 13 Nov 2024 14:31:33 +0100 Subject: [PATCH 375/411] feat: user table is working as expected --- actions/providers/providers.ts | 1 - actions/users.ts | 39 ------ actions/users/users.ts | 116 ++++++++++++++++++ app/(prowler)/providers/page.tsx | 2 +- app/(prowler)/users/page.tsx | 53 ++++---- app/api/users/route.ts | 10 -- components/users/forms/delete-form.tsx | 2 - .../users/table/data-table-row-actions.tsx | 2 +- types/components.ts | 22 ++++ types/formSchemas.ts | 34 +++++ 10 files changed, 202 insertions(+), 79 deletions(-) delete mode 100644 actions/users.ts create mode 100644 actions/users/users.ts delete mode 100644 app/api/users/route.ts diff --git a/actions/providers/providers.ts b/actions/providers/providers.ts index cf079892b4..29a8a114c7 100644 --- a/actions/providers/providers.ts +++ b/actions/providers/providers.ts @@ -307,7 +307,6 @@ export const deleteProvider = async (formData: FormData) => { const response = await fetch(url.toString(), { method: "DELETE", headers: { - Accept: "application/vnd.api+json", Authorization: `Bearer ${session?.accessToken}`, }, }); diff --git a/actions/users.ts b/actions/users.ts deleted file mode 100644 index c4fa97b981..0000000000 --- a/actions/users.ts +++ /dev/null @@ -1,39 +0,0 @@ -"use server"; - -import { revalidatePath } from "next/cache"; -import { redirect } from "next/navigation"; - -import { parseStringify } from "@/lib"; - -export const getUsers = async ({ page = 1 }) => { - if (isNaN(Number(page)) || page < 1) redirect("/users"); - const keyServer = process.env.SITE_URL; - - try { - const users = await fetch( - `${keyServer}/api/users?page%5Bnumber%5D=${page}`, - ); - const data = await users.json(); - const parsedData = parseStringify(data); - revalidatePath("/users"); - return parsedData; - } catch (error) { - console.error("Error fetching Users:", error); - return undefined; - } -}; - -export const getErrorMessage = (error: unknown): string => { - let message: string; - - if (error instanceof Error) { - message = error.message; - } else if (error && typeof error === "object" && "message" in error) { - message = String(error.message); - } else if (typeof error === "string") { - message = error; - } else { - message = "Oops! Something went wrong."; - } - return message; -}; diff --git a/actions/users/users.ts b/actions/users/users.ts new file mode 100644 index 0000000000..903d0be120 --- /dev/null +++ b/actions/users/users.ts @@ -0,0 +1,116 @@ +"use server"; + +import { revalidatePath } from "next/cache"; +import { redirect } from "next/navigation"; + +import { auth } from "@/auth.config"; +import { getErrorMessage, parseStringify, wait } from "@/lib"; + +export const getUsers = async ({ + page = 1, + query = "", + sort = "", + filters = {}, +}) => { + const session = await auth(); + + if (isNaN(Number(page)) || page < 1) redirect("/users"); + + const keyServer = process.env.API_BASE_URL; + const url = new URL(`${keyServer}/users`); + + if (page) url.searchParams.append("page[number]", page.toString()); + if (query) url.searchParams.append("filter[search]", query); + if (sort) url.searchParams.append("sort", sort); + + // Handle multiple filters + Object.entries(filters).forEach(([key, value]) => { + if (key !== "filter[search]") { + url.searchParams.append(key, String(value)); + } + }); + + try { + const users = await fetch(url.toString(), { + headers: { + Accept: "application/vnd.api+json", + Authorization: `Bearer ${session?.accessToken}`, + }, + }); + const data = await users.json(); + const parsedData = parseStringify(data); + revalidatePath("/users"); + return parsedData; + } catch (error) { + console.error("Error fetching users:", error); + return undefined; + } +}; + +export const updateUser = async (formData: FormData) => { + const session = await auth(); + const keyServer = process.env.API_BASE_URL; + + const userId = formData.get("userId"); + const userName = formData.get("name"); + const userPassword = formData.get("password"); + const userEmail = formData.get("email"); + const userCompanyName = formData.get("company_name"); + + const url = new URL(`${keyServer}/users/${userId}`); + + try { + const response = await fetch(url.toString(), { + method: "PATCH", + headers: { + "Content-Type": "application/vnd.api+json", + Accept: "application/vnd.api+json", + Authorization: `Bearer ${session?.accessToken}`, + }, + body: JSON.stringify({ + data: { + type: "User", + id: userId, + attributes: { + name: userName, + password: userPassword, + email: userEmail, + company_name: userCompanyName, + }, + }, + }), + }); + const data = await response.json(); + revalidatePath("/users"); + return parseStringify(data); + } catch (error) { + console.error(error); + return { + error: getErrorMessage(error), + }; + } +}; + +export const deleteUser = async (formData: FormData) => { + const session = await auth(); + const keyServer = process.env.API_BASE_URL; + + const userId = formData.get("userId"); + const url = new URL(`${keyServer}/users/${userId}`); + try { + const response = await fetch(url.toString(), { + method: "DELETE", + headers: { + Authorization: `Bearer ${session?.accessToken}`, + }, + }); + const data = await response.json(); + await wait(1000); + revalidatePath("/users"); + return parseStringify(data); + } catch (error) { + return { + error: getErrorMessage(error), + }; + } +}; diff --git a/app/(prowler)/providers/page.tsx b/app/(prowler)/providers/page.tsx index 177b403745..3e70554f0c 100644 --- a/app/(prowler)/providers/page.tsx +++ b/app/(prowler)/providers/page.tsx @@ -53,7 +53,7 @@ const SSRDataTable = async ({ const query = (filters["filter[search]"] as string) || ""; const providersData = await getProviders({ query, page, sort, filters }); - + console.log(providersData, "since data table"); return ( -
+
-
-
- -
- - }> - - -
+ + + {/* */} + + + }> + + ); } @@ -43,16 +38,24 @@ const SSRDataTable = async ({ searchParams: SearchParamsProps; }) => { const page = parseInt(searchParams.page?.toString() || "1", 10); - const usersData = await getUsers({ page }); - const [users] = await Promise.all([usersData]); + const sort = searchParams.sort?.toString(); - if (users?.errors) redirect("/users"); + // Extract all filter parameters + const filters = Object.fromEntries( + Object.entries(searchParams).filter(([key]) => key.startsWith("filter[")), + ); + + // Extract query from filters + const query = (filters["filter[search]"] as string) || ""; + + const usersData = await getUsers({ query, page, sort, filters }); return ( - ); }; diff --git a/app/api/users/route.ts b/app/api/users/route.ts deleted file mode 100644 index 9905b5c8c9..0000000000 --- a/app/api/users/route.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { NextResponse } from "next/server"; - -import data from "../../../dataUsers.json"; - -export async function GET() { - // Simulate fetching data with a delay - await new Promise((resolve) => setTimeout(resolve, 2000)); - - return NextResponse.json({ users: data }); -} diff --git a/components/users/forms/delete-form.tsx b/components/users/forms/delete-form.tsx index 36843534b9..7128672903 100644 --- a/components/users/forms/delete-form.tsx +++ b/components/users/forms/delete-form.tsx @@ -37,8 +37,6 @@ export const DeleteForm = ({ Object.entries(values).forEach( ([key, value]) => value !== undefined && formData.append(key, value), ); - - console.log(formData); // client-side validation const data = await deleteUser(formData); diff --git a/components/users/table/data-table-row-actions.tsx b/components/users/table/data-table-row-actions.tsx index 79825bf168..07a00cd6a7 100644 --- a/components/users/table/data-table-row-actions.tsx +++ b/components/users/table/data-table-row-actions.tsx @@ -80,7 +80,7 @@ export function DataTableRowActions({ description="Allows you to edit the user" textValue="Edit User" startContent={} - // onClick={() => setIsEditOpen(true)} + onClick={() => setIsEditOpen(true)} > Edit User diff --git a/types/components.ts b/types/components.ts index fb78990811..d753bac9d9 100644 --- a/types/components.ts +++ b/types/components.ts @@ -101,6 +101,28 @@ export interface ApiError { code: string; } +export interface UserProps { + type: "User"; + id: string; + attributes: { + name: string; + email: string; + company_name: string; + date_joined: string; + }; + relationships: { + memberships: { + meta: { + count: number; + }; + data: Array<{ + type: "Membership"; + id: string; + }>; + }; + }; +} + export interface ProviderProps { id: string; type: "providers"; diff --git a/types/formSchemas.ts b/types/formSchemas.ts index a90ffc8c02..333064cfa4 100644 --- a/types/formSchemas.ts +++ b/types/formSchemas.ts @@ -150,3 +150,37 @@ export const editProviderFormSchema = (currentAlias: string) => .optional(), providerId: z.string(), }); + +export const editUserFormSchema = ( + currentName: string, + currentEmail: string, + currentCompanyName: string, +) => + z.object({ + name: z + .string() + .min(3, { message: "The name must have at least 3 characters." }) + .max(150, { message: "The name cannot exceed 150 characters." }) + .refine((val) => val !== currentName, { + message: "The new name must be different from the current one.", + }) + .optional(), + email: z + .string() + .email({ message: "Please enter a valid email address." }) + .refine((val) => val !== currentEmail, { + message: "The new email must be different from the current one.", + }) + .optional(), + password: z + .string() + .min(1, { message: "The password cannot be empty." }) + .optional(), + company_name: z + .string() + .refine((val) => val !== currentCompanyName, { + message: "The new company name must be different from the current one.", + }) + .optional(), + userId: z.string(), + }); From 6d05ad981550d1da53f7821048c69fad55f00ebc Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Wed, 13 Nov 2024 14:32:10 +0100 Subject: [PATCH 376/411] chore: remove unused console log --- app/(prowler)/providers/page.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/app/(prowler)/providers/page.tsx b/app/(prowler)/providers/page.tsx index 3e70554f0c..5b44c3d2d1 100644 --- a/app/(prowler)/providers/page.tsx +++ b/app/(prowler)/providers/page.tsx @@ -53,7 +53,6 @@ const SSRDataTable = async ({ const query = (filters["filter[search]"] as string) || ""; const providersData = await getProviders({ query, page, sort, filters }); - console.log(providersData, "since data table"); return ( Date: Wed, 13 Nov 2024 10:15:33 -0500 Subject: [PATCH 377/411] fix(Dockerfile): ensure correct deployment (#92) * fix(Dockerfile): ensure correct deployment * chore(dockerfile): Add NEXT_TELEMETRY_DISABLED=1 --------- Co-authored-by: Pepe Fagoaga --- .env.template | 1 + Dockerfile | 64 +++++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 56 insertions(+), 9 deletions(-) diff --git a/.env.template b/.env.template index 0ac689045f..ba212ad68a 100644 --- a/.env.template +++ b/.env.template @@ -1,5 +1,6 @@ SITE_URL=http://localhost:3000 API_BASE_URL=http://localhost:8080/api/v1 +AUTH_TRUST_HOST=true # openssl rand -base64 32 AUTH_SECRET=your-secret-key diff --git a/Dockerfile b/Dockerfile index ee47649043..a8c698c014 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,24 +1,70 @@ -# Base image for Node FROM node:20-alpine AS base LABEL maintainer="https://github.com/prowler-cloud" +# Install dependencies only when needed +FROM base AS deps +# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed. +#hadolint ignore=DL3018 +RUN apk add --no-cache libc6-compat WORKDIR /app -# Install dependencies only when needed -RUN apk add --no-cache libc6-compat - -# Copy package.json and lock files to install dependencies +# Install dependencies based on the preferred package manager COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./ -RUN if [ -f package-lock.json ]; then npm install; else echo "Lockfile not found." && exit 1; fi +RUN \ + if [ -f package-lock.json ]; then npm install; \ + else echo "Lockfile not found." && exit 1; \ + fi -# Copy the rest of the application code + +# Rebuild the source code only when needed +FROM base AS builder +WORKDIR /app +COPY --from=deps /app/node_modules ./node_modules COPY . . +# Next.js collects completely anonymous telemetry data about general usage. +# Learn more here: https://nextjs.org/telemetry +# Uncomment the following line in case you want to disable telemetry during the build. +ENV NEXT_TELEMETRY_DISABLED=1 + +RUN \ + if [ -f package-lock.json ]; then npm run build; \ + else echo "Lockfile not found." && exit 1; \ + fi + # Development stage FROM base AS dev +WORKDIR /app + +# Set up environment for development +ENV NODE_ENV=development +ENV NEXT_TELEMETRY_DISABLED=1 +COPY --from=builder /app /app + +# Run development server with hot-reloading CMD ["npm", "run", "dev"] # Production stage FROM base AS prod -RUN npm run build -CMD ["npm", "start"] +WORKDIR /app + +# Set up environment for production +ENV NODE_ENV=production +ENV NEXT_TELEMETRY_DISABLED=1 + +RUN addgroup --system --gid 1001 nodejs &&\ +adduser --system --uid 1001 nextjs + +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static +COPY --from=builder --chown=nextjs:nodejs /app/public ./public + +USER nextjs + +EXPOSE 3000 + +ENV PORT=3000 +ENV HOSTNAME="0.0.0.0" +# server.js is created by next build from the standalone output +# https://nextjs.org/docs/pages/api-reference/next-config-js/output +CMD ["node", "server.js"] From 29dfd303db4f4165e67d09b76a69e0af5a3ba4c6 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Wed, 13 Nov 2024 17:18:32 +0100 Subject: [PATCH 378/411] feat: adding workflow to send invites to the user --- actions/invitations/invitation.ts | 40 +++ .../(send-invite)/check-details/page.tsx | 3 + .../invitations/(send-invite)/layout.tsx | 32 ++ .../invitations/(send-invite)/new/page.tsx | 7 + app/(prowler)/invitations/page.tsx | 62 ++++ .../connect-account/page.tsx | 2 - .../providers/(set-up-provider)/layout.tsx | 4 +- app/(prowler)/users/page.tsx | 3 +- components/invitations/index.ts | 1 + .../invitations/send-invitation-button.tsx | 21 ++ .../invitations/workflow/forms/index.ts | 1 + .../workflow/forms/send-invitation-form.tsx | 107 +++++++ components/invitations/workflow/index.ts | 2 + .../invitations/workflow/vertical-steps.tsx | 291 ++++++++++++++++++ .../workflow/workflow-send-invite.tsx | 64 ++++ components/providers/workflow/index.ts | 2 +- ...workflow.tsx => workflow-add-provider.tsx} | 2 +- components/ui/sidebar/sidebar-items.tsx | 16 +- components/users/add-user-button.tsx | 21 ++ components/users/index.ts | 1 + 20 files changed, 664 insertions(+), 18 deletions(-) create mode 100644 actions/invitations/invitation.ts create mode 100644 app/(prowler)/invitations/(send-invite)/check-details/page.tsx create mode 100644 app/(prowler)/invitations/(send-invite)/layout.tsx create mode 100644 app/(prowler)/invitations/(send-invite)/new/page.tsx create mode 100644 app/(prowler)/invitations/page.tsx create mode 100644 components/invitations/index.ts create mode 100644 components/invitations/send-invitation-button.tsx create mode 100644 components/invitations/workflow/forms/index.ts create mode 100644 components/invitations/workflow/forms/send-invitation-form.tsx create mode 100644 components/invitations/workflow/index.ts create mode 100644 components/invitations/workflow/vertical-steps.tsx create mode 100644 components/invitations/workflow/workflow-send-invite.tsx rename components/providers/workflow/{workflow.tsx => workflow-add-provider.tsx} (98%) create mode 100644 components/users/add-user-button.tsx create mode 100644 components/users/index.ts diff --git a/actions/invitations/invitation.ts b/actions/invitations/invitation.ts new file mode 100644 index 0000000000..7fe129c92f --- /dev/null +++ b/actions/invitations/invitation.ts @@ -0,0 +1,40 @@ +"use server"; + +import { auth } from "@/auth.config"; +import { getErrorMessage, parseStringify } from "@/lib"; +export const sendInvite = async (formData: FormData) => { + const session = await auth(); + const keyServer = process.env.API_BASE_URL; + + const email = formData.get("email"); + const url = new URL(`${keyServer}/tenants/invitations`); + + const body = JSON.stringify({ + data: { + type: "Invitation", + attributes: { + email, + }, + relationships: {}, + }, + }); + + try { + const response = await fetch(url.toString(), { + method: "POST", + headers: { + "Content-Type": "application/vnd.api+json", + Accept: "application/vnd.api+json", + Authorization: `Bearer ${session?.accessToken}`, + }, + body, + }); + const data = await response.json(); + + return parseStringify(data); + } catch (error) { + return { + error: getErrorMessage(error), + }; + } +}; diff --git a/app/(prowler)/invitations/(send-invite)/check-details/page.tsx b/app/(prowler)/invitations/(send-invite)/check-details/page.tsx new file mode 100644 index 0000000000..824d642093 --- /dev/null +++ b/app/(prowler)/invitations/(send-invite)/check-details/page.tsx @@ -0,0 +1,3 @@ +export default function CheckDetailsPage() { + return
CheckDetailsPage
; +} diff --git a/app/(prowler)/invitations/(send-invite)/layout.tsx b/app/(prowler)/invitations/(send-invite)/layout.tsx new file mode 100644 index 0000000000..2af2be2af2 --- /dev/null +++ b/app/(prowler)/invitations/(send-invite)/layout.tsx @@ -0,0 +1,32 @@ +import "@/styles/globals.css"; + +import { Spacer } from "@nextui-org/react"; +import React from "react"; + +import { WorkflowSendInvite } from "@/components/invitations/workflow"; +import { NavigationHeader } from "@/components/ui"; + +interface InvitationLayoutProps { + children: React.ReactNode; +} + +export default function InvitationLayout({ children }: InvitationLayoutProps) { + return ( + <> + + +
+
+ +
+
+ {children} +
+
+ + ); +} diff --git a/app/(prowler)/invitations/(send-invite)/new/page.tsx b/app/(prowler)/invitations/(send-invite)/new/page.tsx new file mode 100644 index 0000000000..87a2e33acd --- /dev/null +++ b/app/(prowler)/invitations/(send-invite)/new/page.tsx @@ -0,0 +1,7 @@ +import React from "react"; + +import { SendInvitationForm } from "@/components/invitations/workflow/forms/send-invitation-form"; + +export default function SendInvitationPage() { + return ; +} diff --git a/app/(prowler)/invitations/page.tsx b/app/(prowler)/invitations/page.tsx new file mode 100644 index 0000000000..6f0df7bc4b --- /dev/null +++ b/app/(prowler)/invitations/page.tsx @@ -0,0 +1,62 @@ +import { Spacer } from "@nextui-org/react"; +import { Suspense } from "react"; + +import { getUsers } from "@/actions/users/users"; +import { FilterControls } from "@/components/filters"; +import { filterUsers } from "@/components/filters/data-filters"; +import { SendInvitationButton } from "@/components/invitations"; +import { Header } from "@/components/ui"; +import { DataTable } from "@/components/ui/table"; +import { ColumnsUser, SkeletonTableUser } from "@/components/users/table"; +import { SearchParamsProps } from "@/types"; + +export default async function Invitations({ + searchParams, +}: { + searchParams: SearchParamsProps; +}) { + const searchParamsKey = JSON.stringify(searchParams || {}); + + return ( + <> +
+ + + + + + + }> + + + + ); +} + +const SSRDataTable = async ({ + searchParams, +}: { + searchParams: SearchParamsProps; +}) => { + const page = parseInt(searchParams.page?.toString() || "1", 10); + const sort = searchParams.sort?.toString(); + + // Extract all filter parameters + const filters = Object.fromEntries( + Object.entries(searchParams).filter(([key]) => key.startsWith("filter[")), + ); + + // Extract query from filters + const query = (filters["filter[search]"] as string) || ""; + + const usersData = await getUsers({ query, page, sort, filters }); + + return ( + + ); +}; diff --git a/app/(prowler)/providers/(set-up-provider)/connect-account/page.tsx b/app/(prowler)/providers/(set-up-provider)/connect-account/page.tsx index 67094101f5..eead6c2561 100644 --- a/app/(prowler)/providers/(set-up-provider)/connect-account/page.tsx +++ b/app/(prowler)/providers/(set-up-provider)/connect-account/page.tsx @@ -1,5 +1,3 @@ -"use client"; - import React from "react"; import { ConnectAccountForm } from "@/components/providers/workflow/forms"; diff --git a/app/(prowler)/providers/(set-up-provider)/layout.tsx b/app/(prowler)/providers/(set-up-provider)/layout.tsx index 788ce9cd96..85b78ee687 100644 --- a/app/(prowler)/providers/(set-up-provider)/layout.tsx +++ b/app/(prowler)/providers/(set-up-provider)/layout.tsx @@ -3,7 +3,7 @@ import "@/styles/globals.css"; import { Spacer } from "@nextui-org/react"; import React from "react"; -import { Workflow } from "@/components/providers/workflow"; +import { WorkflowAddProvider } from "@/components/providers/workflow"; import { NavigationHeader } from "@/components/ui"; interface ProviderLayoutProps { @@ -21,7 +21,7 @@ export default function ProviderLayout({ children }: ProviderLayoutProps) {
- +
{children} diff --git a/app/(prowler)/users/page.tsx b/app/(prowler)/users/page.tsx index fdb6f0de03..805c8271e6 100644 --- a/app/(prowler)/users/page.tsx +++ b/app/(prowler)/users/page.tsx @@ -6,6 +6,7 @@ import { FilterControls } from "@/components/filters"; import { filterUsers } from "@/components/filters/data-filters"; import { Header } from "@/components/ui"; import { DataTable } from "@/components/ui/table"; +import { AddUserButton } from "@/components/users"; import { ColumnsUser, SkeletonTableUser } from "@/components/users/table"; import { SearchParamsProps } from "@/types"; @@ -22,7 +23,7 @@ export default async function Users({ - {/* */} + }> diff --git a/components/invitations/index.ts b/components/invitations/index.ts new file mode 100644 index 0000000000..7eabbf93fe --- /dev/null +++ b/components/invitations/index.ts @@ -0,0 +1 @@ +export * from "./send-invitation-button"; diff --git a/components/invitations/send-invitation-button.tsx b/components/invitations/send-invitation-button.tsx new file mode 100644 index 0000000000..ae13d33b1d --- /dev/null +++ b/components/invitations/send-invitation-button.tsx @@ -0,0 +1,21 @@ +"use client"; + +import { AddIcon } from "../icons"; +import { CustomButton } from "../ui/custom"; + +export const SendInvitationButton = () => { + return ( +
+ } + > + Send Invitation + +
+ ); +}; diff --git a/components/invitations/workflow/forms/index.ts b/components/invitations/workflow/forms/index.ts new file mode 100644 index 0000000000..3180c95b40 --- /dev/null +++ b/components/invitations/workflow/forms/index.ts @@ -0,0 +1 @@ +export * from "./send-invitation-form"; diff --git a/components/invitations/workflow/forms/send-invitation-form.tsx b/components/invitations/workflow/forms/send-invitation-form.tsx new file mode 100644 index 0000000000..39dd42803b --- /dev/null +++ b/components/invitations/workflow/forms/send-invitation-form.tsx @@ -0,0 +1,107 @@ +"use client"; + +import { zodResolver } from "@hookform/resolvers/zod"; +import { SaveIcon } from "lucide-react"; +import { useRouter } from "next/navigation"; +import { useForm } from "react-hook-form"; +import * as z from "zod"; + +import { sendInvite } from "@/actions/invitations/invitation"; +import { useToast } from "@/components/ui"; +import { CustomButton, CustomInput } from "@/components/ui/custom"; +import { Form } from "@/components/ui/form"; +import { ApiError } from "@/types"; + +const sendInvitationFormSchema = z.object({ + email: z.string().email("Please enter a valid email"), +}); + +export type FormValues = z.infer; + +export const SendInvitationForm = () => { + const { toast } = useToast(); + const router = useRouter(); + + const form = useForm({ + resolver: zodResolver(sendInvitationFormSchema), + defaultValues: { + email: "", + }, + }); + + const isLoading = form.formState.isSubmitting; + + const onSubmitClient = async (values: FormValues) => { + const formData = new FormData(); + formData.append("email", values.email); + + try { + const data = await sendInvite(formData); + + if (data?.errors && data.errors.length > 0) { + data.errors.forEach((error: ApiError) => { + const errorMessage = error.detail; + switch (error.source.pointer) { + case "/data/attributes/email": + form.setError("email", { + type: "server", + message: errorMessage, + }); + break; + default: + toast({ + variant: "destructive", + title: "Oops! Something went wrong", + description: errorMessage, + }); + } + }); + } else { + const token = data?.data?.attributes.token || ""; + router.push(`invitations/check-details/?invitation_token=${token}`); + } + } catch (error) { + toast({ + variant: "destructive", + title: "Error", + description: "An unexpected error occurred. Please try again.", + }); + } + }; + + return ( +
+ + + +
+ } + > + {isLoading ? <>Loading : Send Invitation} + +
+ + + ); +}; diff --git a/components/invitations/workflow/index.ts b/components/invitations/workflow/index.ts new file mode 100644 index 0000000000..41589b2616 --- /dev/null +++ b/components/invitations/workflow/index.ts @@ -0,0 +1,2 @@ +export * from "./vertical-steps"; +export * from "./workflow-send-invite"; diff --git a/components/invitations/workflow/vertical-steps.tsx b/components/invitations/workflow/vertical-steps.tsx new file mode 100644 index 0000000000..74eaa32747 --- /dev/null +++ b/components/invitations/workflow/vertical-steps.tsx @@ -0,0 +1,291 @@ +"use client"; + +import type { ButtonProps } from "@nextui-org/react"; +import { cn } from "@nextui-org/react"; +import { useControlledState } from "@react-stately/utils"; +import { domAnimation, LazyMotion, m } from "framer-motion"; +import type { ComponentProps } from "react"; +import React from "react"; + +export type VerticalStepProps = { + className?: string; + description?: React.ReactNode; + title?: React.ReactNode; +}; + +export interface VerticalStepsProps + extends React.HTMLAttributes { + /** + * An array of steps. + * + * @default [] + */ + steps?: VerticalStepProps[]; + /** + * The color of the steps. + * + * @default "primary" + */ + color?: ButtonProps["color"]; + /** + * The current step index. + */ + currentStep?: number; + /** + * The default step index. + * + * @default 0 + */ + defaultStep?: number; + /** + * Whether to hide the progress bars. + * + * @default false + */ + hideProgressBars?: boolean; + /** + * The custom class for the steps wrapper. + */ + className?: string; + /** + * The custom class for the step. + */ + stepClassName?: string; + /** + * Callback function when the step index changes. + */ + onStepChange?: (stepIndex: number) => void; +} + +function CheckIcon(props: ComponentProps<"svg">) { + return ( + + + + ); +} + +export const VerticalSteps = React.forwardRef< + HTMLButtonElement, + VerticalStepsProps +>( + ( + { + color = "primary", + steps = [], + defaultStep = 0, + onStepChange, + currentStep: currentStepProp, + hideProgressBars = false, + stepClassName, + className, + ...props + }, + ref, + ) => { + const [currentStep, setCurrentStep] = useControlledState( + currentStepProp, + defaultStep, + onStepChange, + ); + + const colors = React.useMemo(() => { + let userColor; + let fgColor; + + const colorsVars = [ + "[--active-fg-color:var(--step-fg-color)]", + "[--active-border-color:var(--step-color)]", + "[--active-color:var(--step-color)]", + "[--complete-background-color:var(--step-color)]", + "[--complete-border-color:var(--step-color)]", + "[--inactive-border-color:hsl(var(--nextui-default-300))]", + "[--inactive-color:hsl(var(--nextui-default-300))]", + ]; + + switch (color) { + case "primary": + userColor = "[--step-color:hsl(var(--nextui-primary))]"; + fgColor = "[--step-fg-color:hsl(var(--nextui-primary-foreground))]"; + break; + case "secondary": + userColor = "[--step-color:hsl(var(--nextui-secondary))]"; + fgColor = "[--step-fg-color:hsl(var(--nextui-secondary-foreground))]"; + break; + case "success": + userColor = "[--step-color:hsl(var(--nextui-success))]"; + fgColor = "[--step-fg-color:hsl(var(--nextui-success-foreground))]"; + break; + case "warning": + userColor = "[--step-color:hsl(var(--nextui-warning))]"; + fgColor = "[--step-fg-color:hsl(var(--nextui-warning-foreground))]"; + break; + case "danger": + userColor = "[--step-color:hsl(var(--nextui-error))]"; + fgColor = "[--step-fg-color:hsl(var(--nextui-error-foreground))]"; + break; + case "default": + userColor = "[--step-color:hsl(var(--nextui-default))]"; + fgColor = "[--step-fg-color:hsl(var(--nextui-default-foreground))]"; + break; + default: + userColor = "[--step-color:hsl(var(--nextui-primary))]"; + fgColor = "[--step-fg-color:hsl(var(--nextui-primary-foreground))]"; + break; + } + + if (!className?.includes("--step-fg-color")) colorsVars.unshift(fgColor); + if (!className?.includes("--step-color")) colorsVars.unshift(userColor); + if (!className?.includes("--inactive-bar-color")) + colorsVars.push( + "[--inactive-bar-color:hsl(var(--nextui-default-300))]", + ); + + return colorsVars; + }, [color, className]); + + return ( + + ); + }, +); + +VerticalSteps.displayName = "VerticalSteps"; diff --git a/components/invitations/workflow/workflow-send-invite.tsx b/components/invitations/workflow/workflow-send-invite.tsx new file mode 100644 index 0000000000..cf87d04f06 --- /dev/null +++ b/components/invitations/workflow/workflow-send-invite.tsx @@ -0,0 +1,64 @@ +"use client"; + +import { Progress, Spacer } from "@nextui-org/react"; +import { usePathname } from "next/navigation"; +import React from "react"; + +import { VerticalSteps } from "./vertical-steps"; + +const steps = [ + { + title: "Send Invitation", + description: + "Enter the email address of the person you want to invite and send the invitation.", + href: "/invitations/new", + }, + { + title: "Review Invitation Details", + description: + "Review the invitation details and share the information required for the person to accept the invitation.", + href: "/invitations/check-details", + }, +]; + +export const WorkflowSendInvite = () => { + const pathname = usePathname(); + + // Calculate current step based on pathname + const currentStepIndex = steps.findIndex((step) => + pathname.endsWith(step.href), + ); + const currentStep = currentStepIndex === -1 ? 0 : currentStepIndex; + + return ( +
+

+ Send invitation +

+

+ Follow the steps to send an invitation to the users. +

+ + + +
+ ); +}; diff --git a/components/providers/workflow/index.ts b/components/providers/workflow/index.ts index f245196eab..fca9e92617 100644 --- a/components/providers/workflow/index.ts +++ b/components/providers/workflow/index.ts @@ -1,2 +1,2 @@ export * from "./vertical-steps"; -export * from "./workflow"; +export * from "./workflow-add-provider"; diff --git a/components/providers/workflow/workflow.tsx b/components/providers/workflow/workflow-add-provider.tsx similarity index 98% rename from components/providers/workflow/workflow.tsx rename to components/providers/workflow/workflow-add-provider.tsx index da5fa18f99..69bc885638 100644 --- a/components/providers/workflow/workflow.tsx +++ b/components/providers/workflow/workflow-add-provider.tsx @@ -32,7 +32,7 @@ const steps = [ }, ]; -export const Workflow = () => { +export const WorkflowAddProvider = () => { const pathname = usePathname(); // Calculate current step based on pathname diff --git a/components/ui/sidebar/sidebar-items.tsx b/components/ui/sidebar/sidebar-items.tsx index 32043aadc9..a4fae9eab4 100644 --- a/components/ui/sidebar/sidebar-items.tsx +++ b/components/ui/sidebar/sidebar-items.tsx @@ -29,10 +29,10 @@ export const items: SidebarItem[] = [ ), }, { - key: "tasks", + key: "invitations", href: "#", icon: "solar:checklist-minimalistic-outline", - title: "Tasks", + title: "Invitations", endContent: ( , - // }, ], }, ]; diff --git a/components/users/add-user-button.tsx b/components/users/add-user-button.tsx new file mode 100644 index 0000000000..b4a63fc7a7 --- /dev/null +++ b/components/users/add-user-button.tsx @@ -0,0 +1,21 @@ +"use client"; + +import { AddIcon } from "../icons"; +import { CustomButton } from "../ui/custom"; + +export const AddUserButton = () => { + return ( +
+ } + > + Invite User + +
+ ); +}; diff --git a/components/users/index.ts b/components/users/index.ts new file mode 100644 index 0000000000..014350e9a6 --- /dev/null +++ b/components/users/index.ts @@ -0,0 +1 @@ +export * from "./add-user-button"; From 890bd12e995cbdd76df6e41335ccf3b79d3a8089 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Wed, 13 Nov 2024 18:52:06 +0100 Subject: [PATCH 379/411] feat: workflow to invite an user is working --- actions/invitations/invitation.ts | 24 ++++ .../(send-invite)/check-details/page.tsx | 44 ++++++- components/invitations/index.ts | 1 + components/invitations/invitation-details.tsx | 112 ++++++++++++++++++ .../workflow/forms/send-invitation-form.tsx | 4 +- components/invitations/workflow/index.ts | 1 + .../workflow/skeleton-invitation-info.tsx | 65 ++++++++++ types/components.ts | 24 ++++ 8 files changed, 271 insertions(+), 4 deletions(-) create mode 100644 components/invitations/invitation-details.tsx create mode 100644 components/invitations/workflow/skeleton-invitation-info.tsx diff --git a/actions/invitations/invitation.ts b/actions/invitations/invitation.ts index 7fe129c92f..1a11900422 100644 --- a/actions/invitations/invitation.ts +++ b/actions/invitations/invitation.ts @@ -2,6 +2,7 @@ import { auth } from "@/auth.config"; import { getErrorMessage, parseStringify } from "@/lib"; + export const sendInvite = async (formData: FormData) => { const session = await auth(); const keyServer = process.env.API_BASE_URL; @@ -38,3 +39,26 @@ export const sendInvite = async (formData: FormData) => { }; } }; + +export const getInvitationInfoById = async (invitationId: string) => { + const session = await auth(); + const keyServer = process.env.API_BASE_URL; + const url = new URL(`${keyServer}/tenants/invitations/${invitationId}`); + + try { + const response = await fetch(url.toString(), { + method: "GET", + headers: { + Accept: "application/vnd.api+json", + Authorization: `Bearer ${session?.accessToken}`, + }, + }); + + const data = await response.json(); + return parseStringify(data); + } catch (error) { + return { + error: getErrorMessage(error), + }; + } +}; diff --git a/app/(prowler)/invitations/(send-invite)/check-details/page.tsx b/app/(prowler)/invitations/(send-invite)/check-details/page.tsx index 824d642093..939667cdc1 100644 --- a/app/(prowler)/invitations/(send-invite)/check-details/page.tsx +++ b/app/(prowler)/invitations/(send-invite)/check-details/page.tsx @@ -1,3 +1,43 @@ -export default function CheckDetailsPage() { - return
CheckDetailsPage
; +import { Suspense } from "react"; + +import { getInvitationInfoById } from "@/actions/invitations/invitation"; +import { InvitationDetails } from "@/components/invitations"; +import { SkeletonInvitationInfo } from "@/components/invitations/workflow"; +import { SearchParamsProps } from "@/types"; + +export default async function CheckDetailsPage({ + searchParams, +}: { + searchParams: SearchParamsProps; +}) { + const searchParamsKey = JSON.stringify(searchParams || {}); + + return ( + }> + + + ); } + +const SSRDataInvitation = async ({ + searchParams, +}: { + searchParams: SearchParamsProps; +}) => { + const invitationId = searchParams.id; + + if (!invitationId) { + return
Invalid invitation ID
; + } + + const invitationData = (await getInvitationInfoById(invitationId as string)) + .data; + + if (!invitationData) { + return
Invitation not found
; + } + + const { attributes, links } = invitationData; + + return ; +}; diff --git a/components/invitations/index.ts b/components/invitations/index.ts index 7eabbf93fe..e381a114df 100644 --- a/components/invitations/index.ts +++ b/components/invitations/index.ts @@ -1 +1,2 @@ +export * from "./invitation-details"; export * from "./send-invitation-button"; diff --git a/components/invitations/invitation-details.tsx b/components/invitations/invitation-details.tsx new file mode 100644 index 0000000000..12c6209920 --- /dev/null +++ b/components/invitations/invitation-details.tsx @@ -0,0 +1,112 @@ +"use client"; + +import { Card, CardBody, Divider, Snippet } from "@nextui-org/react"; + +import { AddIcon } from "../icons"; +import { CustomButton } from "../ui/custom"; +import { DateWithTime } from "../ui/entities"; + +interface InvitationDetailsProps { + attributes: { + email: string; + state: string; + token: string; + expires_at: string; + inserted_at: string; + updated_at: string; + }; + relationships?: { + inviter: { + data: { + id: string; + }; + }; + }; + selfLink: string; +} + +export const InvitationDetails = ({ + attributes, + selfLink, +}: InvitationDetailsProps) => { + return ( +
+ + +

+ Invitation Details +

+ + +
+
+ Email: + {attributes.email} +
+ +
+ State: + {attributes.state} +
+ +
+ Token: + {attributes.token} +
+ +
+ Expires At: + +
+ +
+ Inserted At: + +
+ +
+ Updated At: + +
+
+ + +

+ Share this link with the user: +

+ +
+ +

+ {selfLink} +

+
+
+
+
+
+ } + > + Back to Invitations + +
+
+ ); +}; diff --git a/components/invitations/workflow/forms/send-invitation-form.tsx b/components/invitations/workflow/forms/send-invitation-form.tsx index 39dd42803b..a75c6a60c6 100644 --- a/components/invitations/workflow/forms/send-invitation-form.tsx +++ b/components/invitations/workflow/forms/send-invitation-form.tsx @@ -57,8 +57,8 @@ export const SendInvitationForm = () => { } }); } else { - const token = data?.data?.attributes.token || ""; - router.push(`invitations/check-details/?invitation_token=${token}`); + const invitationId = data?.data?.id || ""; + router.push(`/invitations/check-details/?id=${invitationId}`); } } catch (error) { toast({ diff --git a/components/invitations/workflow/index.ts b/components/invitations/workflow/index.ts index 41589b2616..a9a860f213 100644 --- a/components/invitations/workflow/index.ts +++ b/components/invitations/workflow/index.ts @@ -1,2 +1,3 @@ +export * from "./skeleton-invitation-info"; export * from "./vertical-steps"; export * from "./workflow-send-invite"; diff --git a/components/invitations/workflow/skeleton-invitation-info.tsx b/components/invitations/workflow/skeleton-invitation-info.tsx new file mode 100644 index 0000000000..12abeb9da7 --- /dev/null +++ b/components/invitations/workflow/skeleton-invitation-info.tsx @@ -0,0 +1,65 @@ +import { Card, Skeleton } from "@nextui-org/react"; +import React from "react"; + +export const SkeletonInvitationInfo = () => { + return ( + + {/* Table headers */} +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+ + {/* Table body */} +
+ {[...Array(3)].map((_, index) => ( +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+ ))} +
+
+ ); +}; diff --git a/types/components.ts b/types/components.ts index d753bac9d9..2299d0bffe 100644 --- a/types/components.ts +++ b/types/components.ts @@ -101,6 +101,30 @@ export interface ApiError { code: string; } +export interface InvitationProps { + type: "Invitation"; + id: string; + attributes: { + inserted_at: string; + updated_at: string; + email: string; + state: string; + token: string; + expires_at: string; + }; + relationships: { + inviter: { + data: { + type: "User"; + id: string; + }; + }; + }; + links: { + self: string; + }; +} + export interface UserProps { type: "User"; id: string; From 1dc4bd313a7fd097f2a245cea6ac3b81df5d3eb6 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Thu, 14 Nov 2024 08:08:08 +0100 Subject: [PATCH 380/411] feat: invitation workflow is working as expected --- actions/invitations/invitation.ts | 110 +++++++++++++++- app/(prowler)/invitations/page.tsx | 21 ++-- components/filters/data-filters.ts | 8 ++ components/invitations/forms/delete-form.tsx | 95 ++++++++++++++ components/invitations/forms/edit-form.tsx | 118 ++++++++++++++++++ components/invitations/forms/index.ts | 2 + .../invitations/table/column-invitations.tsx | 72 +++++++++++ .../table/data-table-row-actions.tsx | 117 +++++++++++++++++ components/invitations/table/index.ts | 3 + .../table/skeleton-table-invitations.tsx | 59 +++++++++ types/formSchemas.ts | 6 + 11 files changed, 601 insertions(+), 10 deletions(-) create mode 100644 components/invitations/forms/delete-form.tsx create mode 100644 components/invitations/forms/edit-form.tsx create mode 100644 components/invitations/forms/index.ts create mode 100644 components/invitations/table/column-invitations.tsx create mode 100644 components/invitations/table/data-table-row-actions.tsx create mode 100644 components/invitations/table/index.ts create mode 100644 components/invitations/table/skeleton-table-invitations.tsx diff --git a/actions/invitations/invitation.ts b/actions/invitations/invitation.ts index 1a11900422..7fc99ce33c 100644 --- a/actions/invitations/invitation.ts +++ b/actions/invitations/invitation.ts @@ -1,7 +1,51 @@ "use server"; +import { revalidatePath } from "next/cache"; +import { redirect } from "next/navigation"; + import { auth } from "@/auth.config"; -import { getErrorMessage, parseStringify } from "@/lib"; +import { getErrorMessage, parseStringify, wait } from "@/lib"; + +export const getInvitations = async ({ + page = 1, + query = "", + sort = "", + filters = {}, +}) => { + const session = await auth(); + + if (isNaN(Number(page)) || page < 1) redirect("/invitations"); + + const keyServer = process.env.API_BASE_URL; + const url = new URL(`${keyServer}/tenants/invitations`); + + if (page) url.searchParams.append("page[number]", page.toString()); + if (query) url.searchParams.append("filter[search]", query); + if (sort) url.searchParams.append("sort", sort); + + // Handle multiple filters + Object.entries(filters).forEach(([key, value]) => { + if (key !== "filter[search]") { + url.searchParams.append(key, String(value)); + } + }); + + try { + const invitations = await fetch(url.toString(), { + headers: { + Accept: "application/vnd.api+json", + Authorization: `Bearer ${session?.accessToken}`, + }, + }); + const data = await invitations.json(); + const parsedData = parseStringify(data); + revalidatePath("/invitations"); + return parsedData; + } catch (error) { + console.error("Error fetching invitations:", error); + return undefined; + } +}; export const sendInvite = async (formData: FormData) => { const session = await auth(); @@ -40,6 +84,46 @@ export const sendInvite = async (formData: FormData) => { } }; +export const updateInvite = async (formData: FormData) => { + const session = await auth(); + const keyServer = process.env.API_BASE_URL; + + const invitationId = formData.get("invitationId"); + const invitationEmail = formData.get("invitationEmail"); + const expiresAt = formData.get("expires_at"); + + const url = new URL(`${keyServer}/tenants/invitations/${invitationId}`); + + try { + const response = await fetch(url.toString(), { + method: "PATCH", + headers: { + "Content-Type": "application/vnd.api+json", + Accept: "application/vnd.api+json", + Authorization: `Bearer ${session?.accessToken}`, + }, + body: JSON.stringify({ + data: { + type: "Invitation", + id: invitationId, + attributes: { + email: invitationEmail, + ...(expiresAt && { expires_at: expiresAt }), + }, + }, + }), + }); + const data = await response.json(); + revalidatePath("/invitations"); + return parseStringify(data); + } catch (error) { + console.error("Error updating invitation:", error); + return { + error: getErrorMessage(error), + }; + } +}; + export const getInvitationInfoById = async (invitationId: string) => { const session = await auth(); const keyServer = process.env.API_BASE_URL; @@ -62,3 +146,27 @@ export const getInvitationInfoById = async (invitationId: string) => { }; } }; + +export const revokeInvite = async (formData: FormData) => { + const session = await auth(); + const keyServer = process.env.API_BASE_URL; + + const invitationId = formData.get("invitationId"); + const url = new URL(`${keyServer}/tenants/invitations/${invitationId}`); + try { + const response = await fetch(url.toString(), { + method: "DELETE", + headers: { + Authorization: `Bearer ${session?.accessToken}`, + }, + }); + const data = await response.json(); + await wait(1000); + revalidatePath("/invitations"); + return parseStringify(data); + } catch (error) { + return { + error: getErrorMessage(error), + }; + } +}; diff --git a/app/(prowler)/invitations/page.tsx b/app/(prowler)/invitations/page.tsx index 6f0df7bc4b..b32573cd45 100644 --- a/app/(prowler)/invitations/page.tsx +++ b/app/(prowler)/invitations/page.tsx @@ -1,13 +1,16 @@ import { Spacer } from "@nextui-org/react"; import { Suspense } from "react"; -import { getUsers } from "@/actions/users/users"; +import { getInvitations } from "@/actions/invitations/invitation"; import { FilterControls } from "@/components/filters"; -import { filterUsers } from "@/components/filters/data-filters"; +import { filterInvitations } from "@/components/filters/data-filters"; import { SendInvitationButton } from "@/components/invitations"; +import { + ColumnsInvitation, + SkeletonTableInvitation, +} from "@/components/invitations/table"; import { Header } from "@/components/ui"; import { DataTable } from "@/components/ui/table"; -import { ColumnsUser, SkeletonTableUser } from "@/components/users/table"; import { SearchParamsProps } from "@/types"; export default async function Invitations({ @@ -26,7 +29,7 @@ export default async function Invitations({ - }> + }> @@ -49,14 +52,14 @@ const SSRDataTable = async ({ // Extract query from filters const query = (filters["filter[search]"] as string) || ""; - const usersData = await getUsers({ query, page, sort, filters }); + const invitationsData = await getInvitations({ query, page, sort, filters }); return ( ); }; diff --git a/components/filters/data-filters.ts b/components/filters/data-filters.ts index 21691b2c5c..91a039605d 100644 --- a/components/filters/data-filters.ts +++ b/components/filters/data-filters.ts @@ -59,3 +59,11 @@ export const filterUsers = [ values: ["true", "false"], }, ]; + +export const filterInvitations = [ + { + key: "state", + labelCheckboxGroup: "State", + values: ["pending", "accepted", "expired", "revoked"], + }, +]; diff --git a/components/invitations/forms/delete-form.tsx b/components/invitations/forms/delete-form.tsx new file mode 100644 index 0000000000..35f81bbaad --- /dev/null +++ b/components/invitations/forms/delete-form.tsx @@ -0,0 +1,95 @@ +"use client"; + +import { zodResolver } from "@hookform/resolvers/zod"; +import React, { Dispatch, SetStateAction } from "react"; +import { useForm } from "react-hook-form"; +import * as z from "zod"; + +import { revokeInvite } from "@/actions/invitations/invitation"; +import { DeleteIcon } from "@/components/icons"; +import { useToast } from "@/components/ui"; +import { CustomButton } from "@/components/ui/custom"; +import { Form } from "@/components/ui/form"; + +const formSchema = z.object({ + invitationId: z.string(), +}); + +export const DeleteForm = ({ + invitationId, + setIsOpen, +}: { + invitationId: string; + setIsOpen: Dispatch>; +}) => { + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + invitationId, + }, + }); + const { toast } = useToast(); + const isLoading = form.formState.isSubmitting; + + async function onSubmitClient(values: z.infer) { + const formData = new FormData(); + + Object.entries(values).forEach( + ([key, value]) => value !== undefined && formData.append(key, value), + ); + // client-side validation + const data = await revokeInvite(formData); + + if (data?.errors && data.errors.length > 0) { + const error = data.errors[0]; + const errorMessage = `${error.detail}`; + // show error + toast({ + variant: "destructive", + title: "Oops! Something went wrong", + description: errorMessage, + }); + } else { + toast({ + title: "Success!", + description: "The invitation was revoked successfully.", + }); + } + setIsOpen(false); // Close the modal on success + } + + return ( +
+ + +
+ setIsOpen(false)} + isDisabled={isLoading} + > + Cancel + + + } + > + {isLoading ? <>Loading : Delete} + +
+
+ + ); +}; diff --git a/components/invitations/forms/edit-form.tsx b/components/invitations/forms/edit-form.tsx new file mode 100644 index 0000000000..17924cea97 --- /dev/null +++ b/components/invitations/forms/edit-form.tsx @@ -0,0 +1,118 @@ +"use client"; + +import { zodResolver } from "@hookform/resolvers/zod"; +import { Dispatch, SetStateAction } from "react"; +import { useForm } from "react-hook-form"; +import * as z from "zod"; + +import { updateInvite } from "@/actions/invitations/invitation"; +import { SaveIcon } from "@/components/icons"; +import { useToast } from "@/components/ui"; +import { CustomButton, CustomInput } from "@/components/ui/custom"; +import { Form } from "@/components/ui/form"; +import { editInviteFormSchema } from "@/types"; + +export const EditForm = ({ + invitationId, + invitationEmail, + setIsOpen, +}: { + invitationId: string; + invitationEmail?: string; + setIsOpen: Dispatch>; +}) => { + const formSchema = editInviteFormSchema; + + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + invitationId, + invitationEmail: invitationEmail, + }, + }); + + const { toast } = useToast(); + + const isLoading = form.formState.isSubmitting; + + const onSubmitClient = async (values: z.infer) => { + const formData = new FormData(); + console.log(values); + + Object.entries(values).forEach( + ([key, value]) => value !== undefined && formData.append(key, value), + ); + + const data = await updateInvite(formData); + + if (data?.error) { + const errorMessage = `${data.error}`; + toast({ + variant: "destructive", + title: "Oops! Something went wrong", + description: errorMessage, + }); + } else { + toast({ + title: "Success!", + description: "The invitation was updated successfully.", + }); + setIsOpen(false); // Close the modal on success + } + }; + + return ( +
+ +
+ Current email: {invitationEmail} +
+
+ +
+ + +
+ setIsOpen(false)} + isDisabled={isLoading} + > + Cancel + + + } + > + {isLoading ? <>Loading : Save} + +
+
+ + ); +}; diff --git a/components/invitations/forms/index.ts b/components/invitations/forms/index.ts new file mode 100644 index 0000000000..a081952ade --- /dev/null +++ b/components/invitations/forms/index.ts @@ -0,0 +1,2 @@ +export * from "./delete-form"; +export * from "./edit-form"; diff --git a/components/invitations/table/column-invitations.tsx b/components/invitations/table/column-invitations.tsx new file mode 100644 index 0000000000..9e30eb8024 --- /dev/null +++ b/components/invitations/table/column-invitations.tsx @@ -0,0 +1,72 @@ +"use client"; + +import { ColumnDef } from "@tanstack/react-table"; + +import { DateWithTime } from "@/components/ui/entities"; +import { DataTableColumnHeader } from "@/components/ui/table"; +import { InvitationProps } from "@/types"; + +import { DataTableRowActions } from "./data-table-row-actions"; + +const getInvitationData = (row: { original: InvitationProps }) => { + return row.original.attributes; +}; + +export const ColumnsInvitation: ColumnDef[] = [ + { + accessorKey: "email", + header: () =>
Email
, + cell: ({ row }) => { + const data = getInvitationData(row); + return

{data?.email || "N/A"}

; + }, + }, + { + accessorKey: "state", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + const { state } = getInvitationData(row); + return

{state}

; + }, + }, + { + accessorKey: "inserted_at", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + const { inserted_at } = getInvitationData(row); + return ; + }, + }, + + { + accessorKey: "expires_at", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + const { expires_at } = getInvitationData(row); + return ; + }, + }, + + { + accessorKey: "actions", + header: () =>
Actions
, + id: "actions", + cell: ({ row }) => { + return ; + }, + }, +]; diff --git a/components/invitations/table/data-table-row-actions.tsx b/components/invitations/table/data-table-row-actions.tsx new file mode 100644 index 0000000000..580ffaeb0c --- /dev/null +++ b/components/invitations/table/data-table-row-actions.tsx @@ -0,0 +1,117 @@ +"use client"; + +import { + Button, + Dropdown, + DropdownItem, + DropdownMenu, + DropdownSection, + DropdownTrigger, +} from "@nextui-org/react"; +import { + AddNoteBulkIcon, + DeleteDocumentBulkIcon, + EditDocumentBulkIcon, +} from "@nextui-org/shared-icons"; +import { Row } from "@tanstack/react-table"; +import clsx from "clsx"; +import { useState } from "react"; + +import { VerticalDotsIcon } from "@/components/icons"; +import { CustomAlertModal } from "@/components/ui/custom"; + +import { DeleteForm, EditForm } from "../forms"; + +interface DataTableRowActionsProps { + row: Row; +} +const iconClasses = + "text-2xl text-default-500 pointer-events-none flex-shrink-0"; + +export function DataTableRowActions({ + row, +}: DataTableRowActionsProps) { + const [isEditOpen, setIsEditOpen] = useState(false); + const [isDeleteOpen, setIsDeleteOpen] = useState(false); + const invitationId = (row.original as { id: string }).id; + const invitationEmail = (row.original as any).attributes?.email; + return ( + <> + + + + + + + +
+ + + + + + + } + > + Check Details + + + } + onClick={() => setIsEditOpen(true)} + > + Edit Invitation + + + + + } + onClick={() => setIsDeleteOpen(true)} + > + Delete Invitation + + + + +
+ + ); +} diff --git a/components/invitations/table/index.ts b/components/invitations/table/index.ts new file mode 100644 index 0000000000..177edf2bb9 --- /dev/null +++ b/components/invitations/table/index.ts @@ -0,0 +1,3 @@ +export * from "./column-invitations"; +export * from "./data-table-row-actions"; +export * from "./skeleton-table-invitations"; diff --git a/components/invitations/table/skeleton-table-invitations.tsx b/components/invitations/table/skeleton-table-invitations.tsx new file mode 100644 index 0000000000..6d18313fe4 --- /dev/null +++ b/components/invitations/table/skeleton-table-invitations.tsx @@ -0,0 +1,59 @@ +import { Card, Skeleton } from "@nextui-org/react"; +import React from "react"; + +export const SkeletonTableInvitation = () => { + return ( + + {/* Table headers */} +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+ + {/* Table body */} +
+ {[...Array(10)].map((_, index) => ( +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+ ))} +
+
+ ); +}; diff --git a/types/formSchemas.ts b/types/formSchemas.ts index 333064cfa4..4238df3ef2 100644 --- a/types/formSchemas.ts +++ b/types/formSchemas.ts @@ -151,6 +151,12 @@ export const editProviderFormSchema = (currentAlias: string) => providerId: z.string(), }); +export const editInviteFormSchema = z.object({ + invitationId: z.string().uuid(), + invitationEmail: z.string().email(), + expires_at: z.string().optional(), +}); + export const editUserFormSchema = ( currentName: string, currentEmail: string, From 58068b34bf5bf6d679776a5133bf84f7a543e215 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Thu, 14 Nov 2024 11:55:11 +0100 Subject: [PATCH 381/411] feat: invitations are working - first iteration --- actions/auth/auth.ts | 5 ++++- app/(auth)/sign-up/page.tsx | 10 ++++++++-- components/auth/oss/auth-form.tsx | 25 ++++++++++++++++++++++++- types/authFormSchema.ts | 2 ++ 4 files changed, 38 insertions(+), 4 deletions(-) diff --git a/actions/auth/auth.ts b/actions/auth/auth.ts index e0de4422b6..4973e3f8e2 100644 --- a/actions/auth/auth.ts +++ b/actions/auth/auth.ts @@ -56,6 +56,10 @@ export const createNewUser = async ( const keyServer = process.env.API_BASE_URL; const url = new URL(`${keyServer}/users`); + if (formData.invitationToken) { + url.searchParams.append("invitation_token", formData.invitationToken); + } + const bodyData = { data: { type: "User", @@ -79,7 +83,6 @@ export const createNewUser = async ( }); const parsedResponse = await response.json(); - if (!response.ok) { return parsedResponse; } diff --git a/app/(auth)/sign-up/page.tsx b/app/(auth)/sign-up/page.tsx index 8e525507b8..28149bb200 100644 --- a/app/(auth)/sign-up/page.tsx +++ b/app/(auth)/sign-up/page.tsx @@ -1,7 +1,13 @@ import { AuthForm } from "@/components/auth/oss"; +import { SearchParamsProps } from "@/types"; -const SignUp = () => { - return ; +const SignUp = ({ searchParams }: { searchParams: SearchParamsProps }) => { + const invitationToken = + typeof searchParams?.invitation_token === "string" + ? searchParams.invitation_token + : null; + + return ; }; export default SignUp; diff --git a/components/auth/oss/auth-form.tsx b/components/auth/oss/auth-form.tsx index 2afe281f2a..1be8e9e5e5 100644 --- a/components/auth/oss/auth-form.tsx +++ b/components/auth/oss/auth-form.tsx @@ -20,7 +20,13 @@ import { } from "@/components/ui/form"; import { ApiError, authFormSchema } from "@/types"; -export const AuthForm = ({ type }: { type: string }) => { +export const AuthForm = ({ + type, + invitationToken, +}: { + type: string; + invitationToken?: string | null; +}) => { const formSchema = authFormSchema(type); const router = useRouter(); @@ -96,6 +102,12 @@ export const AuthForm = ({ type }: { type: string }) => { message: errorMessage, }); break; + case "/data/attributes/invitation_token": + form.setError("invitationToken", { + type: "server", + message: errorMessage, + }); + break; default: toast({ variant: "destructive", @@ -186,6 +198,17 @@ export const AuthForm = ({ type }: { type: string }) => { name="confirmPassword" confirmPassword /> + {invitationToken && ( + + )} : z.string().min(12, { message: "It must contain at least 12 characters.", }), + invitationToken: + type === "sign-in" ? z.string().optional() : z.string().optional(), termsAndConditions: type === "sign-in" ? z.boolean().optional() From e21386c1d5229062f1edf7c3fb136e0d2fe2f8b5 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Sat, 16 Nov 2024 12:48:23 +0100 Subject: [PATCH 382/411] chore: Show the error in the after the invitation token field --- components/auth/oss/auth-form.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/components/auth/oss/auth-form.tsx b/components/auth/oss/auth-form.tsx index 1be8e9e5e5..1f1ec4384f 100644 --- a/components/auth/oss/auth-form.tsx +++ b/components/auth/oss/auth-form.tsx @@ -40,6 +40,7 @@ export const AuthForm = ({ company: "", termsAndConditions: false, confirmPassword: "", + ...(invitationToken && { invitationToken }), }), }, }); @@ -102,7 +103,7 @@ export const AuthForm = ({ message: errorMessage, }); break; - case "/data/attributes/invitation_token": + case "/data": form.setError("invitationToken", { type: "server", message: errorMessage, @@ -205,6 +206,7 @@ export const AuthForm = ({ type="text" label="Invitation Token" placeholder={invitationToken} + defaultValue={invitationToken} isRequired={false} isInvalid={!!form.formState.errors.invitationToken} /> From 4fd5d868c617f7cda981c27fbf4cd64aa1c4ba99 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Sat, 16 Nov 2024 12:49:33 +0100 Subject: [PATCH 383/411] chore: change label for revoke invitations --- components/invitations/invitation-details.tsx | 9 ++++----- components/invitations/table/data-table-row-actions.tsx | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/components/invitations/invitation-details.tsx b/components/invitations/invitation-details.tsx index 12c6209920..7739cf8adb 100644 --- a/components/invitations/invitation-details.tsx +++ b/components/invitations/invitation-details.tsx @@ -25,10 +25,9 @@ interface InvitationDetailsProps { selfLink: string; } -export const InvitationDetails = ({ - attributes, - selfLink, -}: InvitationDetailsProps) => { +export const InvitationDetails = ({ attributes }: InvitationDetailsProps) => { + const baseURL = process.env.SITE_URL || "http://localhost:3000"; + const invitationLink = `${baseURL}/sign-up?invitation_token=${attributes.token}`; return (

- {selfLink} + {invitationLink}

diff --git a/components/invitations/table/data-table-row-actions.tsx b/components/invitations/table/data-table-row-actions.tsx index 580ffaeb0c..b743f8aeba 100644 --- a/components/invitations/table/data-table-row-actions.tsx +++ b/components/invitations/table/data-table-row-actions.tsx @@ -106,7 +106,7 @@ export function DataTableRowActions({ } onClick={() => setIsDeleteOpen(true)} > - Delete Invitation + Revoke Invitation From 3f5f50fe380019774706ffdfda995623e6e01b68 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Sat, 16 Nov 2024 12:50:22 +0100 Subject: [PATCH 384/411] chore: add defaultValue prop to the CustomInput component --- components/ui/custom/custom-input.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/ui/custom/custom-input.tsx b/components/ui/custom/custom-input.tsx index a391142344..2b390b83c0 100644 --- a/components/ui/custom/custom-input.tsx +++ b/components/ui/custom/custom-input.tsx @@ -18,6 +18,7 @@ interface CustomInputProps { placeholder?: string; password?: boolean; confirmPassword?: boolean; + defaultValue?: string; isRequired?: boolean; isInvalid?: boolean; } @@ -33,6 +34,7 @@ export const CustomInput = ({ size = "md", confirmPassword = false, password = false, + defaultValue, isRequired = true, isInvalid, }: CustomInputProps) => { @@ -99,6 +101,7 @@ export const CustomInput = ({ variant={variant} size={size} isInvalid={isInvalid} + defaultValue={defaultValue} endContent={endContent} {...field} /> From 01bc745478052bd89f79c609534becbf9ab1f9f6 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Sat, 16 Nov 2024 12:59:30 +0100 Subject: [PATCH 385/411] chore: replace 'delete' with 'revoke' in invitations --- components/invitations/forms/delete-form.tsx | 4 ++-- components/invitations/table/data-table-row-actions.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/components/invitations/forms/delete-form.tsx b/components/invitations/forms/delete-form.tsx index 35f81bbaad..bea018b2ae 100644 --- a/components/invitations/forms/delete-form.tsx +++ b/components/invitations/forms/delete-form.tsx @@ -78,7 +78,7 @@ export const DeleteForm = ({ } > - {isLoading ? <>Loading : Delete} + {isLoading ? <>Loading : Revoke}
diff --git a/components/invitations/table/data-table-row-actions.tsx b/components/invitations/table/data-table-row-actions.tsx index b743f8aeba..2a17c944dc 100644 --- a/components/invitations/table/data-table-row-actions.tsx +++ b/components/invitations/table/data-table-row-actions.tsx @@ -53,7 +53,7 @@ export function DataTableRowActions({ isOpen={isDeleteOpen} onOpenChange={setIsDeleteOpen} title="Are you absolutely sure?" - description="This action cannot be undone. This will permanently delete your invitation." + description="This action cannot be undone. This will permanently revoke your invitation." > From d1424b3c9c417c893e4d19bcc52a03ef19fb81a7 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Sat, 16 Nov 2024 15:57:28 +0100 Subject: [PATCH 386/411] fix: resolve breaking changes caused by updated API specs --- actions/auth/auth.ts | 4 ++-- actions/invitations/invitation.ts | 4 ++-- actions/providers/providers.ts | 8 ++++---- actions/scans/scans.ts | 6 +++--- actions/users/users.ts | 2 +- auth.config.ts | 2 +- types/components.ts | 30 +++++++++++++++--------------- 7 files changed, 28 insertions(+), 28 deletions(-) diff --git a/actions/auth/auth.ts b/actions/auth/auth.ts index 4973e3f8e2..d5ec199b06 100644 --- a/actions/auth/auth.ts +++ b/actions/auth/auth.ts @@ -62,7 +62,7 @@ export const createNewUser = async ( const bodyData = { data: { - type: "User", + type: "users", attributes: { name: formData.name, email: formData.email, @@ -99,7 +99,7 @@ export const getToken = async (formData: z.infer) => { const bodyData = { data: { - type: "Token", + type: "tokens", attributes: { email: formData.email, password: formData.password, diff --git a/actions/invitations/invitation.ts b/actions/invitations/invitation.ts index 7fc99ce33c..654d9fd982 100644 --- a/actions/invitations/invitation.ts +++ b/actions/invitations/invitation.ts @@ -56,7 +56,7 @@ export const sendInvite = async (formData: FormData) => { const body = JSON.stringify({ data: { - type: "Invitation", + type: "invitations", attributes: { email, }, @@ -104,7 +104,7 @@ export const updateInvite = async (formData: FormData) => { }, body: JSON.stringify({ data: { - type: "Invitation", + type: "invitations", id: invitationId, attributes: { email: invitationEmail, diff --git a/actions/providers/providers.ts b/actions/providers/providers.ts index 29a8a114c7..fc5cd97bb7 100644 --- a/actions/providers/providers.ts +++ b/actions/providers/providers.ts @@ -90,7 +90,7 @@ export const updateProvider = async (formData: FormData) => { }, body: JSON.stringify({ data: { - type: "Provider", + type: "providers", id: providerId, attributes: { alias: providerAlias, @@ -129,7 +129,7 @@ export const addProvider = async (formData: FormData) => { }, body: JSON.stringify({ data: { - type: "Provider", + type: "providers", attributes: { provider: providerType, uid: providerUid, @@ -209,7 +209,7 @@ export const addCredentialsProvider = async (formData: FormData) => { const bodyData = { data: { - type: "ProviderSecret", + type: "provider-secrets", attributes: { secret_type: secretType, secret, @@ -219,7 +219,7 @@ export const addCredentialsProvider = async (formData: FormData) => { provider: { data: { id: providerId, - type: "Provider", + type: "providers", }, }, }, diff --git a/actions/scans/scans.ts b/actions/scans/scans.ts index 866918cf86..367446daaa 100644 --- a/actions/scans/scans.ts +++ b/actions/scans/scans.ts @@ -91,14 +91,14 @@ export const scanOnDemand = async (formData: FormData) => { }, body: JSON.stringify({ data: { - type: "Scan", + type: "scans", attributes: { name: scanName, }, relationships: { provider: { data: { - type: "Provider", + type: "providers", id: providerId, }, }, @@ -136,7 +136,7 @@ export const updateScan = async (formData: FormData) => { }, body: JSON.stringify({ data: { - type: "Scan", + type: "scans", id: scanId, attributes: { name: scanName, diff --git a/actions/users/users.ts b/actions/users/users.ts index 903d0be120..30a5b26d1f 100644 --- a/actions/users/users.ts +++ b/actions/users/users.ts @@ -69,7 +69,7 @@ export const updateUser = async (formData: FormData) => { }, body: JSON.stringify({ data: { - type: "User", + type: "users", id: userId, attributes: { name: userName, diff --git a/auth.config.ts b/auth.config.ts index 4639f8bf37..56c44a14e5 100644 --- a/auth.config.ts +++ b/auth.config.ts @@ -16,7 +16,7 @@ const refreshAccessToken = async (token: JwtPayload) => { const bodyData = { data: { - type: "TokenRefresh", + type: "tokens-refresh", attributes: { refresh: (token as any).refreshToken, }, diff --git a/types/components.ts b/types/components.ts index 2299d0bffe..52b553cce0 100644 --- a/types/components.ts +++ b/types/components.ts @@ -102,7 +102,7 @@ export interface ApiError { } export interface InvitationProps { - type: "Invitation"; + type: "invitations"; id: string; attributes: { inserted_at: string; @@ -115,7 +115,7 @@ export interface InvitationProps { relationships: { inviter: { data: { - type: "User"; + type: "users"; id: string; }; }; @@ -126,7 +126,7 @@ export interface InvitationProps { } export interface UserProps { - type: "User"; + type: "users"; id: string; attributes: { name: string; @@ -140,7 +140,7 @@ export interface UserProps { count: number; }; data: Array<{ - type: "Membership"; + type: "memberships"; id: string; }>; }; @@ -175,7 +175,7 @@ export interface ProviderProps { } export interface ScanProps { - type: "Scan"; + type: "scans"; id: string; attributes: { name: string; @@ -203,20 +203,20 @@ export interface ScanProps { provider: { data: { id: string; - type: "Provider"; + type: "providers"; }; }; task: { data: { id: string; - type: "Task"; + type: "tasks"; }; }; }; } export interface FindingProps { - type: "Finding"; + type: "findings"; id: string; attributes: { uid: string; @@ -264,13 +264,13 @@ export interface FindingProps { relationships: { resources: { data: { - type: "Resource"; + type: "resources"; id: string; }[]; }; scan: { data: { - type: "Scan"; + type: "scans"; id: string; }; attributes: { @@ -290,7 +290,7 @@ export interface FindingProps { }; resource: { data: { - type: "Resource"; + type: "resources"; id: string; }[]; id: string; @@ -307,7 +307,7 @@ export interface FindingProps { relationships: { provider: { data: { - type: "Provider"; + type: "providers"; id: string; }; }; @@ -316,7 +316,7 @@ export interface FindingProps { count: number; }; data: { - type: "Finding"; + type: "findings"; id: string; }[]; }; @@ -327,7 +327,7 @@ export interface FindingProps { }; provider: { data: { - type: "Provider"; + type: "providers"; id: string; }; attributes: { @@ -344,7 +344,7 @@ export interface FindingProps { relationships: { secret: { data: { - type: "ProviderSecret"; + type: "provider-secrets"; id: string; }; }; From e92bbffc532fc405bc139105422f581de875cfca Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Sat, 16 Nov 2024 21:13:41 +0100 Subject: [PATCH 387/411] chore: delete old dummy data for compliances dashboard --- actions/compliance.ts | 39 ---- app/api/compliance/route.ts | 10 - dataCompliance.json | 438 ------------------------------------ 3 files changed, 487 deletions(-) delete mode 100644 actions/compliance.ts delete mode 100644 app/api/compliance/route.ts delete mode 100644 dataCompliance.json diff --git a/actions/compliance.ts b/actions/compliance.ts deleted file mode 100644 index 304b826077..0000000000 --- a/actions/compliance.ts +++ /dev/null @@ -1,39 +0,0 @@ -"use server"; - -import { revalidatePath } from "next/cache"; -import { redirect } from "next/navigation"; - -import { parseStringify } from "@/lib"; - -export const getCompliance = async ({ page = 1 }) => { - if (isNaN(Number(page)) || page < 1) redirect("/compliance"); - const keyServer = process.env.SITE_URL; - - try { - const compliance = await fetch( - `${keyServer}/api/compliance?page%5Bnumber%5D=${page}`, - ); - const data = await compliance.json(); - const parsedData = parseStringify(data); - revalidatePath("/compliance"); - return parsedData; - } catch (error) { - console.error("Error fetching Compliance:", error); - return undefined; - } -}; - -export const getErrorMessage = (error: unknown): string => { - let message: string; - - if (error instanceof Error) { - message = error.message; - } else if (error && typeof error === "object" && "message" in error) { - message = String(error.message); - } else if (typeof error === "string") { - message = error; - } else { - message = "Oops! Something went wrong."; - } - return message; -}; diff --git a/app/api/compliance/route.ts b/app/api/compliance/route.ts deleted file mode 100644 index b2e4a01d9f..0000000000 --- a/app/api/compliance/route.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { NextResponse } from "next/server"; - -import data from "../../../dataCompliance.json"; - -export async function GET() { - // Simulate fetching data with a delay - await new Promise((resolve) => setTimeout(resolve, 2000)); - - return NextResponse.json({ compliance: data }); -} diff --git a/dataCompliance.json b/dataCompliance.json deleted file mode 100644 index a1bdd62831..0000000000 --- a/dataCompliance.json +++ /dev/null @@ -1,438 +0,0 @@ -{ - "links": { - "first": "http://localhost:8080/api/v1/compliance?page%5Bnumber%5D=1", - "last": "http://localhost:8080/api/v1/compliance?page%5Bnumber%5D=1", - "next": null, - "prev": null - }, - "data": [ - { - "id": "001", - "attributes": { - "title": "AWS Account Security Onboarding", - "passingRequirements": 28, - "totalRequirements": 83, - "regions": ["us-east-1", "us-east-2"] - }, - "lastScan": { - "attributes": { - "passingRequirements": 28, - "totalRequirements": 83 - } - } - }, - { - "id": "002", - "attributes": { - "title": "AWS Audit Manager Control Tower Guardrails", - "passingRequirements": 10, - "totalRequirements": 14, - "regions": ["us-west-1", "us-west-2"] - }, - "lastScan": { - "attributes": { - "passingRequirements": 14, - "totalRequirements": 14 - } - } - }, - { - "id": "003", - "attributes": { - "title": "AWS Foundational Security Best Practices", - "passingRequirements": 22, - "totalRequirements": 37, - "regions": ["eu-west-1", "eu-west-2"] - }, - "lastScan": { - "attributes": { - "passingRequirements": 22, - "totalRequirements": 37 - } - } - }, - { - "id": "004", - "attributes": { - "title": "AWS Foundational Technical Review", - "passingRequirements": 3, - "totalRequirements": 45, - "regions": ["ap-southeast-1", "ap-southeast-2"] - }, - "lastScan": { - "attributes": { - "passingRequirements": 25, - "totalRequirements": 45 - } - } - }, - { - "id": "005", - "attributes": { - "title": "AWS Well Architected Framework Reliability Pillar", - "passingRequirements": 2, - "totalRequirements": 3, - "regions": [] - }, - "lastScan": { - "attributes": { - "passingRequirements": 0, - "totalRequirements": 3 - } - } - }, - { - "id": "006", - "attributes": { - "title": "AWS Well Architected Framework Security Pillar", - "passingRequirements": 19, - "totalRequirements": 57, - "regions": [] - }, - "lastScan": { - "attributes": { - "passingRequirements": 24, - "totalRequirements": 57 - } - } - }, - { - "id": "007", - "attributes": { - "title": "CIS 1.4", - "passingRequirements": 43, - "totalRequirements": 58, - "regions": [] - }, - "lastScan": { - "attributes": { - "passingRequirements": 40, - "totalRequirements": 58 - } - } - }, - { - "id": "008", - "attributes": { - "title": "CIS 1.5", - "passingRequirements": 48, - "totalRequirements": 63, - "regions": [] - }, - "lastScan": { - "attributes": { - "passingRequirements": 50, - "totalRequirements": 63 - } - } - }, - { - "id": "009", - "attributes": { - "title": "CIS 2.0", - "passingRequirements": 47, - "totalRequirements": 64, - "regions": [] - }, - "lastScan": { - "attributes": { - "passingRequirements": 64, - "totalRequirements": 64 - } - } - }, - { - "id": "010", - "attributes": { - "title": "CIS 3.0", - "passingRequirements": 45, - "totalRequirements": 62, - "regions": [] - }, - "lastScan": { - "attributes": { - "passingRequirements": 48, - "totalRequirements": 62 - } - } - }, - { - "id": "011", - "attributes": { - "title": "CISA", - "passingRequirements": 4, - "totalRequirements": 16, - "regions": [] - }, - "lastScan": { - "attributes": { - "passingRequirements": 4, - "totalRequirements": 16 - } - } - }, - { - "id": "012", - "attributes": { - "title": "ENS RD2022 Categoría ALTA", - "passingRequirements": 89, - "totalRequirements": 189, - "regions": [] - }, - "lastScan": { - "attributes": { - "passingRequirements": 82, - "totalRequirements": 189 - } - } - }, - { - "id": "013", - "attributes": { - "title": "FFIEC", - "passingRequirements": 8, - "totalRequirements": 44, - "regions": [] - }, - "lastScan": { - "attributes": { - "passingRequirements": 40, - "totalRequirements": 44 - } - } - }, - { - "id": "014", - "attributes": { - "title": "FedRAMP Low Revision 4", - "passingRequirements": 4, - "totalRequirements": 18, - "regions": [] - }, - "lastScan": { - "attributes": { - "passingRequirements": 4, - "totalRequirements": 18 - } - } - }, - { - "id": "015", - "attributes": { - "title": "FedRamp Moderate Revision 4", - "passingRequirements": 11, - "totalRequirements": 64, - "regions": [] - }, - "lastScan": { - "attributes": { - "passingRequirements": 28, - "totalRequirements": 64 - } - } - }, - { - "id": "016", - "attributes": { - "title": "GDPR", - "passingRequirements": 0, - "totalRequirements": 3, - "regions": [] - }, - "lastScan": { - "attributes": { - "passingRequirements": 0, - "totalRequirements": 3 - } - } - }, - { - "id": "017", - "attributes": { - "title": "GxP 21 CFR Part 11", - "passingRequirements": 1, - "totalRequirements": 11, - "regions": [] - }, - "lastScan": { - "attributes": { - "passingRequirements": 11, - "totalRequirements": 11 - } - } - }, - { - "id": "018", - "attributes": { - "title": "GxP EU Annex 11", - "passingRequirements": 5, - "totalRequirements": 14, - "regions": [] - }, - "lastScan": { - "attributes": { - "passingRequirements": 5, - "totalRequirements": 14 - } - } - }, - { - "id": "019", - "attributes": { - "title": "HIPAA", - "passingRequirements": 2, - "totalRequirements": 32, - "regions": [] - }, - "lastScan": { - "attributes": { - "passingRequirements": 9, - "totalRequirements": 32 - } - } - }, - { - "id": "020", - "attributes": { - "title": "ISO27001 2013", - "passingRequirements": 55, - "totalRequirements": 79, - "regions": [] - }, - "lastScan": { - "attributes": { - "passingRequirements": 66, - "totalRequirements": 79 - } - } - }, - { - "id": "021", - "attributes": { - "title": "MITRE ATTACK", - "passingRequirements": 7, - "totalRequirements": 46, - "regions": [] - }, - "lastScan": { - "attributes": { - "passingRequirements": 19, - "totalRequirements": 46 - } - } - }, - { - "id": "022", - "attributes": { - "title": "NIST 800 171 Revision 2", - "passingRequirements": 3, - "totalRequirements": 50, - "regions": [] - }, - "lastScan": { - "attributes": { - "passingRequirements": 3, - "totalRequirements": 50 - } - } - }, - { - "id": "023", - "attributes": { - "title": "NIST 800 53 Revision 4", - "passingRequirements": 21, - "totalRequirements": 64, - "regions": [] - }, - "lastScan": { - "attributes": { - "passingRequirements": 21, - "totalRequirements": 64 - } - } - }, - { - "id": "024", - "attributes": { - "title": "NIST 800 53 Revision 5", - "passingRequirements": 75, - "totalRequirements": 288, - "regions": [] - }, - "lastScan": { - "attributes": { - "passingRequirements": 75, - "totalRequirements": 288 - } - } - }, - { - "id": "025", - "attributes": { - "title": "NIST CSF 1.1", - "passingRequirements": 15, - "totalRequirements": 56, - "regions": [] - }, - "lastScan": { - "attributes": { - "passingRequirements": 46, - "totalRequirements": 56 - } - } - }, - { - "id": "026", - "attributes": { - "title": "PCI 3.2.1", - "passingRequirements": 11, - "totalRequirements": 19, - "regions": [] - }, - "lastScan": { - "attributes": { - "passingRequirements": 19, - "totalRequirements": 19 - } - } - }, - { - "id": "027", - "attributes": { - "title": "RBI Cyber Security Framework", - "passingRequirements": 3, - "totalRequirements": 9, - "regions": [] - }, - "lastScan": { - "attributes": { - "passingRequirements": 3, - "totalRequirements": 9 - } - } - }, - { - "id": "028", - "attributes": { - "title": "SOC2", - "passingRequirements": 7, - "totalRequirements": 56, - "regions": [] - }, - "lastScan": { - "attributes": { - "passingRequirements": 7, - "totalRequirements": 56 - } - } - } - ], - "meta": { - "pagination": { - "page": 1, - "pages": 1, - "count": 28 - }, - "version": "v1" - } -} From eb40369c30b7a2f6e1e6fdd6ab62eff617a8804b Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Mon, 18 Nov 2024 07:45:19 +0100 Subject: [PATCH 388/411] chore: render an message if there is no data for compliances --- actions/compliances/compliances.ts | 34 ++++++++++++++++ actions/compliances/index.ts | 1 + actions/invitations/index.ts | 1 + actions/invitations/invitation.ts | 2 + app/(prowler)/compliance/page.tsx | 64 ++++++++++++++++-------------- types/components.ts | 47 ++++++++++++++++++++++ 6 files changed, 120 insertions(+), 29 deletions(-) create mode 100644 actions/compliances/compliances.ts create mode 100644 actions/compliances/index.ts create mode 100644 actions/invitations/index.ts diff --git a/actions/compliances/compliances.ts b/actions/compliances/compliances.ts new file mode 100644 index 0000000000..6b254acff0 --- /dev/null +++ b/actions/compliances/compliances.ts @@ -0,0 +1,34 @@ +"use server"; +import { revalidatePath } from "next/cache"; + +import { auth } from "@/auth.config"; +import { parseStringify } from "@/lib"; + +export const getCompliancesOverview = async ({ + scanId, +}: { + scanId: string; +}) => { + const session = await auth(); + + const keyServer = process.env.API_BASE_URL; + const url = new URL( + `${keyServer}/compliance-overviews?filter[scan_id]=${scanId}`, + ); + try { + const compliances = await fetch(url.toString(), { + headers: { + Accept: "application/vnd.api+json", + Authorization: `Bearer ${session?.accessToken}`, + }, + }); + const data = await compliances.json(); + const parsedData = parseStringify(data); + revalidatePath("/compliance"); + return parsedData; + } catch (error) { + // eslint-disable-next-line no-console + console.error("Error fetching providers:", error); + return undefined; + } +}; diff --git a/actions/compliances/index.ts b/actions/compliances/index.ts new file mode 100644 index 0000000000..ba9df7e860 --- /dev/null +++ b/actions/compliances/index.ts @@ -0,0 +1 @@ +export * from "./compliances"; diff --git a/actions/invitations/index.ts b/actions/invitations/index.ts new file mode 100644 index 0000000000..addd041884 --- /dev/null +++ b/actions/invitations/index.ts @@ -0,0 +1 @@ +export * from "./invitation"; diff --git a/actions/invitations/invitation.ts b/actions/invitations/invitation.ts index 654d9fd982..56aefc74e1 100644 --- a/actions/invitations/invitation.ts +++ b/actions/invitations/invitation.ts @@ -42,6 +42,7 @@ export const getInvitations = async ({ revalidatePath("/invitations"); return parsedData; } catch (error) { + // eslint-disable-next-line no-console console.error("Error fetching invitations:", error); return undefined; } @@ -117,6 +118,7 @@ export const updateInvite = async (formData: FormData) => { revalidatePath("/invitations"); return parseStringify(data); } catch (error) { + // eslint-disable-next-line no-console console.error("Error updating invitation:", error); return { error: getErrorMessage(error), diff --git a/app/(prowler)/compliance/page.tsx b/app/(prowler)/compliance/page.tsx index 6cc1c5c11e..7e47723441 100644 --- a/app/(prowler)/compliance/page.tsx +++ b/app/(prowler)/compliance/page.tsx @@ -1,15 +1,14 @@ import { Spacer } from "@nextui-org/react"; -import { redirect } from "next/navigation"; import { Suspense } from "react"; -import { getCompliance } from "@/actions/compliance"; +import { getCompliancesOverview } from "@/actions/compliances"; import { ComplianceCard, ComplianceSkeletonGrid, } from "@/components/compliance"; import { FilterControls } from "@/components/filters"; import { Header } from "@/components/ui"; -import { SearchParamsProps } from "@/types"; +import { ComplianceOverviewData, SearchParamsProps } from "@/types"; export default async function Compliance({ searchParams, @@ -23,41 +22,48 @@ export default async function Compliance({
- + }> - + ); } -const SSRComplianceGrid = async ({ - searchParams, -}: { - searchParams: SearchParamsProps; -}) => { - const page = parseInt(searchParams.page?.toString() || "1", 10); - const compliancesData = await getCompliance({ page }); - const [compliances] = await Promise.all([compliancesData]); +const SSRComplianceGrid = async () => { + // const scanId = "01929f57-c0ee-7553-be0b-cbde006fb6f7"; + const scanId = "0193358c-bd7f-7eec-b13a-2d4a648b8df"; + const compliancesData = await getCompliancesOverview({ scanId }); + console.log(compliancesData, "compliancesData"); - if (compliances?.errors) redirect("/compliance"); + if (compliancesData?.errors?.length > 0) { + return ( +
+
There is no compliance data.
+
+ ); + } return ( -
- {compliances.compliance?.data.map((compliance: any) => ( - - ))} +
+ {compliancesData?.data?.map((compliance: ComplianceOverviewData) => { + const { attributes } = compliance; + const { + framework, + requirements_status: { passed, total }, + } = attributes; + + return ( + + ); + })}
); }; diff --git a/types/components.ts b/types/components.ts index 52b553cce0..7ec383d408 100644 --- a/types/components.ts +++ b/types/components.ts @@ -100,6 +100,53 @@ export interface ApiError { }; code: string; } +export interface CompliancesOverview { + links: { + first: string; + last: string; + next: string | null; + prev: string | null; + }; + data: ComplianceOverviewData[]; + meta: { + pagination: { + page: number; + pages: number; + count: number; + }; + version: string; + }; +} + +export interface ComplianceOverviewData { + type: "compliance-overviews"; + id: string; + attributes: { + inserted_at: string; + compliance_id: string; + framework: string; + version: string; + requirements_status: { + passed: number; + failed: number; + manual: number; + total: number; + }; + region: string; + provider_type: string; + }; + relationships: { + scan: { + data: { + type: "scans"; + id: string; + }; + }; + }; + links: { + self: string; + }; +} export interface InvitationProps { type: "invitations"; From 783db5c3dcd4e5c565df875710d8890674deedf8 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Mon, 18 Nov 2024 08:21:54 +0100 Subject: [PATCH 389/411] feat: allow compliance data selection by choosing a scan --- app/(prowler)/compliance/page.tsx | 23 +++++--- ...ComplianceCard.tsx => compliance-card.tsx} | 0 ...nGrid.tsx => compliance-skeleton-grid.tsx} | 0 .../data-compliance/data-compliance.tsx | 31 +++++++++++ .../compliance/data-compliance/index.ts | 2 + .../select-scan-compliance-data.tsx | 52 +++++++++++++++++++ components/compliance/index.ts | 4 +- 7 files changed, 103 insertions(+), 9 deletions(-) rename components/compliance/{ComplianceCard.tsx => compliance-card.tsx} (100%) rename components/compliance/{ComplianceSkeletonGrid.tsx => compliance-skeleton-grid.tsx} (100%) create mode 100644 components/compliance/data-compliance/data-compliance.tsx create mode 100644 components/compliance/data-compliance/index.ts create mode 100644 components/compliance/data-compliance/select-scan-compliance-data.tsx diff --git a/app/(prowler)/compliance/page.tsx b/app/(prowler)/compliance/page.tsx index 7e47723441..8d3f8315f1 100644 --- a/app/(prowler)/compliance/page.tsx +++ b/app/(prowler)/compliance/page.tsx @@ -2,10 +2,12 @@ import { Spacer } from "@nextui-org/react"; import { Suspense } from "react"; import { getCompliancesOverview } from "@/actions/compliances"; +import { getScans } from "@/actions/scans"; import { ComplianceCard, ComplianceSkeletonGrid, } from "@/components/compliance"; +import { DataCompliance } from "@/components/compliance/data-compliance"; import { FilterControls } from "@/components/filters"; import { Header } from "@/components/ui"; import { ComplianceOverviewData, SearchParamsProps } from "@/types"; @@ -15,26 +17,33 @@ export default async function Compliance({ }: { searchParams: SearchParamsProps; }) { - const searchParamsKey = JSON.stringify(searchParams || {}); + const scansData = await getScans({}); + const scanList = scansData?.data.map((scan: any) => ({ + id: scan.id, + name: scan.attributes.name || "Unnamed Scan", + state: scan.attributes.state, + progress: scan.attributes.progress, + })); + const selectedScanId = searchParams.scanId || scanList[0]?.id; return ( <>
+
+ +
- }> - + }> + ); } -const SSRComplianceGrid = async () => { - // const scanId = "01929f57-c0ee-7553-be0b-cbde006fb6f7"; - const scanId = "0193358c-bd7f-7eec-b13a-2d4a648b8df"; +const SSRComplianceGrid = async ({ scanId }: { scanId: string }) => { const compliancesData = await getCompliancesOverview({ scanId }); - console.log(compliancesData, "compliancesData"); if (compliancesData?.errors?.length > 0) { return ( diff --git a/components/compliance/ComplianceCard.tsx b/components/compliance/compliance-card.tsx similarity index 100% rename from components/compliance/ComplianceCard.tsx rename to components/compliance/compliance-card.tsx diff --git a/components/compliance/ComplianceSkeletonGrid.tsx b/components/compliance/compliance-skeleton-grid.tsx similarity index 100% rename from components/compliance/ComplianceSkeletonGrid.tsx rename to components/compliance/compliance-skeleton-grid.tsx diff --git a/components/compliance/data-compliance/data-compliance.tsx b/components/compliance/data-compliance/data-compliance.tsx new file mode 100644 index 0000000000..52484f6bd7 --- /dev/null +++ b/components/compliance/data-compliance/data-compliance.tsx @@ -0,0 +1,31 @@ +"use client"; + +import { useRouter, useSearchParams } from "next/navigation"; + +import { SelectScanComplianceData } from "@/components/compliance/data-compliance"; + +interface DataComplianceProps { + scans: { id: string; name: string; state: string; progress: number }[]; +} + +export const DataCompliance = ({ scans }: DataComplianceProps) => { + const router = useRouter(); + const searchParams = useSearchParams(); + const selectedScanId = searchParams.get("scanId") || scans[0]?.id; + + const handleSelectionChange = (selectedKey: string) => { + router.push(`?scanId=${selectedKey}`); + }; + + return ( +
+
+ +
+
+ ); +}; diff --git a/components/compliance/data-compliance/index.ts b/components/compliance/data-compliance/index.ts new file mode 100644 index 0000000000..14f430df04 --- /dev/null +++ b/components/compliance/data-compliance/index.ts @@ -0,0 +1,2 @@ +export * from "./data-compliance"; +export * from "./select-scan-compliance-data"; diff --git a/components/compliance/data-compliance/select-scan-compliance-data.tsx b/components/compliance/data-compliance/select-scan-compliance-data.tsx new file mode 100644 index 0000000000..fb1189019c --- /dev/null +++ b/components/compliance/data-compliance/select-scan-compliance-data.tsx @@ -0,0 +1,52 @@ +"use client"; + +import { Select, SelectItem } from "@nextui-org/react"; + +interface SelectScanComplianceDataProps { + scans: { id: string; name: string; state: string; progress: number }[]; + selectedScanId: string; + onSelectionChange: (selectedKey: string) => void; +} + +export const SelectScanComplianceData = ({ + scans, + selectedScanId, + onSelectionChange, +}: SelectScanComplianceDataProps) => { + return ( + + ); +}; diff --git a/components/compliance/index.ts b/components/compliance/index.ts index 6f025f2ca5..bb1b06d995 100644 --- a/components/compliance/index.ts +++ b/components/compliance/index.ts @@ -1,2 +1,2 @@ -export * from "./ComplianceCard"; -export * from "./ComplianceSkeletonGrid"; +export * from "./compliance-card"; +export * from "./compliance-skeleton-grid"; From 223073e3df1aee84b2eb748c718556620073723f Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Mon, 18 Nov 2024 09:07:07 +0100 Subject: [PATCH 390/411] feat: allow compliance data selection by choosing a scan --- app/(prowler)/compliance/page.tsx | 14 +++++++++++++- .../compliance/data-compliance/data-compliance.tsx | 9 ++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/app/(prowler)/compliance/page.tsx b/app/(prowler)/compliance/page.tsx index 8d3f8315f1..68a9a89dbe 100644 --- a/app/(prowler)/compliance/page.tsx +++ b/app/(prowler)/compliance/page.tsx @@ -45,10 +45,22 @@ export default async function Compliance({ const SSRComplianceGrid = async ({ scanId }: { scanId: string }) => { const compliancesData = await getCompliancesOverview({ scanId }); + // Check if the response contains no data + if (!compliancesData || compliancesData?.data?.length === 0) { + return ( +
+
+ No compliance data available for the selected scan. +
+
+ ); + } + + // Handle errors returned by the API if (compliancesData?.errors?.length > 0) { return (
-
There is no compliance data.
+
Provide a valid scan ID.
); } diff --git a/components/compliance/data-compliance/data-compliance.tsx b/components/compliance/data-compliance/data-compliance.tsx index 52484f6bd7..67e45f9ff4 100644 --- a/components/compliance/data-compliance/data-compliance.tsx +++ b/components/compliance/data-compliance/data-compliance.tsx @@ -11,7 +11,14 @@ interface DataComplianceProps { export const DataCompliance = ({ scans }: DataComplianceProps) => { const router = useRouter(); const searchParams = useSearchParams(); - const selectedScanId = searchParams.get("scanId") || scans[0]?.id; + const scanIdParam = searchParams.get("scanId"); + const selectedScanId = + scanIdParam === "undefined" || !scanIdParam ? scans[0]?.id : scanIdParam; + + if (scanIdParam === "undefined") { + router.replace("/compliance"); + return null; + } const handleSelectionChange = (selectedKey: string) => { router.push(`?scanId=${selectedKey}`); From 985efc67cc37b406de893d5887923cc05b61f261 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Mon, 18 Nov 2024 14:05:36 +0100 Subject: [PATCH 391/411] feat: first iteration of compliance dashboard is working --- actions/compliances/compliances.ts | 15 +++- app/(prowler)/compliance/page.tsx | 48 +++++++++--- .../data-compliance/data-compliance.tsx | 74 ++++++++++++++++--- .../select-scan-compliance-data.tsx | 2 - 4 files changed, 112 insertions(+), 27 deletions(-) diff --git a/actions/compliances/compliances.ts b/actions/compliances/compliances.ts index 6b254acff0..e9c8814c31 100644 --- a/actions/compliances/compliances.ts +++ b/actions/compliances/compliances.ts @@ -6,15 +6,23 @@ import { parseStringify } from "@/lib"; export const getCompliancesOverview = async ({ scanId, + region, }: { scanId: string; + region?: string | string[]; }) => { const session = await auth(); const keyServer = process.env.API_BASE_URL; - const url = new URL( - `${keyServer}/compliance-overviews?filter[scan_id]=${scanId}`, - ); + const url = new URL(`${keyServer}/compliance-overviews`); + + if (scanId) url.searchParams.append("filter[scan_id]", scanId); + + if (region) { + const regionValue = Array.isArray(region) ? region.join(",") : region; + url.searchParams.append("filter[region__in]", regionValue); + } + try { const compliances = await fetch(url.toString(), { headers: { @@ -24,6 +32,7 @@ export const getCompliancesOverview = async ({ }); const data = await compliances.json(); const parsedData = parseStringify(data); + revalidatePath("/compliance"); return parsedData; } catch (error) { diff --git a/app/(prowler)/compliance/page.tsx b/app/(prowler)/compliance/page.tsx index 68a9a89dbe..08368c69da 100644 --- a/app/(prowler)/compliance/page.tsx +++ b/app/(prowler)/compliance/page.tsx @@ -8,7 +8,6 @@ import { ComplianceSkeletonGrid, } from "@/components/compliance"; import { DataCompliance } from "@/components/compliance/data-compliance"; -import { FilterControls } from "@/components/filters"; import { Header } from "@/components/ui"; import { ComplianceOverviewData, SearchParamsProps } from "@/types"; @@ -24,26 +23,53 @@ export default async function Compliance({ state: scan.attributes.state, progress: scan.attributes.progress, })); + const selectedScanId = searchParams.scanId || scanList[0]?.id; + // Fetch compliance data for regions + const compliancesData = await getCompliancesOverview({ + scanId: selectedScanId, + }); + + // Extract unique regions + const regions = compliancesData?.data + ? Array.from( + new Set( + compliancesData.data.map( + (compliance: ComplianceOverviewData) => + compliance.attributes.region as string, + ), + ), + ) + : []; + return ( <>
-
- -
- - + + }> - + ); } -const SSRComplianceGrid = async ({ scanId }: { scanId: string }) => { - const compliancesData = await getCompliancesOverview({ scanId }); +const SSRComplianceGrid = async ({ + searchParams, +}: { + searchParams: SearchParamsProps; +}) => { + const scanId = searchParams.scanId?.toString() || ""; + + const regionFilter = searchParams["filter[region__in]"]?.toString() || ""; + + // Fetch compliance data + const compliancesData = await getCompliancesOverview({ + scanId, + region: regionFilter, + }); // Check if the response contains no data if (!compliancesData || compliancesData?.data?.length === 0) { @@ -66,8 +92,8 @@ const SSRComplianceGrid = async ({ scanId }: { scanId: string }) => { } return ( -
- {compliancesData?.data?.map((compliance: ComplianceOverviewData) => { +
+ {compliancesData.data.map((compliance: ComplianceOverviewData) => { const { attributes } = compliance; const { framework, diff --git a/components/compliance/data-compliance/data-compliance.tsx b/components/compliance/data-compliance/data-compliance.tsx index 67e45f9ff4..10fcc44f6b 100644 --- a/components/compliance/data-compliance/data-compliance.tsx +++ b/components/compliance/data-compliance/data-compliance.tsx @@ -1,37 +1,89 @@ "use client"; import { useRouter, useSearchParams } from "next/navigation"; +import { useCallback, useEffect, useState } from "react"; import { SelectScanComplianceData } from "@/components/compliance/data-compliance"; +import { CrossIcon } from "@/components/icons"; +import { CustomButton, CustomDropdownFilter } from "@/components/ui/custom"; interface DataComplianceProps { scans: { id: string; name: string; state: string; progress: number }[]; + regions: string[]; } -export const DataCompliance = ({ scans }: DataComplianceProps) => { +export const DataCompliance = ({ scans, regions }: DataComplianceProps) => { const router = useRouter(); const searchParams = useSearchParams(); + const [showClearButton, setShowClearButton] = useState(false); const scanIdParam = searchParams.get("scanId"); - const selectedScanId = - scanIdParam === "undefined" || !scanIdParam ? scans[0]?.id : scanIdParam; + const selectedScanId = scanIdParam || scans[0]?.id; - if (scanIdParam === "undefined") { - router.replace("/compliance"); - return null; - } - - const handleSelectionChange = (selectedKey: string) => { - router.push(`?scanId=${selectedKey}`); + useEffect(() => { + const hasFilters = Array.from(searchParams.keys()).some( + (key) => key.startsWith("filter[") || key === "sort", + ); + setShowClearButton(hasFilters); + }, [searchParams]); + const handleScanChange = (selectedKey: string) => { + const params = new URLSearchParams(searchParams); + params.set("scanId", selectedKey); + router.push(`?${params.toString()}`); }; + const pushDropdownFilter = useCallback( + (key: string, values: string[]) => { + const params = new URLSearchParams(searchParams); + const filterKey = `filter[${key}]`; + + if (values.length === 0) { + params.delete(filterKey); + } else { + params.set(filterKey, values.join(",")); + } + + router.push(`?${params.toString()}`); + }, + [router, searchParams], + ); + const clearAllFilters = useCallback(() => { + const params = new URLSearchParams(searchParams.toString()); + Array.from(params.keys()).forEach((key) => { + if (key.startsWith("filter[") || key === "sort") { + params.delete(key); + } + }); + router.push(`?${params.toString()}`, { scroll: false }); + }, [router, searchParams]); return (
+ + {showClearButton && ( + } + radius="sm" + > + Reset + + )}
); diff --git a/components/compliance/data-compliance/select-scan-compliance-data.tsx b/components/compliance/data-compliance/select-scan-compliance-data.tsx index fb1189019c..5ebc36515d 100644 --- a/components/compliance/data-compliance/select-scan-compliance-data.tsx +++ b/components/compliance/data-compliance/select-scan-compliance-data.tsx @@ -1,5 +1,3 @@ -"use client"; - import { Select, SelectItem } from "@nextui-org/react"; interface SelectScanComplianceDataProps { From e84fd1fd653899cbe2fb75579c93a44a35d6d1e1 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Tue, 19 Nov 2024 12:39:35 +0100 Subject: [PATCH 392/411] fix: change types because changed in the API specs. --- app/(prowler)/findings/page.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/(prowler)/findings/page.tsx b/app/(prowler)/findings/page.tsx index 4a0a80b8dd..bbfdde2198 100644 --- a/app/(prowler)/findings/page.tsx +++ b/app/(prowler)/findings/page.tsx @@ -53,9 +53,9 @@ const SSRDataTable = async ({ const findingsData = await getFindings({ query, page, sort, filters }); // Create dictionaries for resources, scans, and providers - const resourceDict = createDict("Resource", findingsData); - const scanDict = createDict("Scan", findingsData); - const providerDict = createDict("Provider", findingsData); + const resourceDict = createDict("resources", findingsData); + const scanDict = createDict("scans", findingsData); + const providerDict = createDict("providers", findingsData); // Expand each finding with its corresponding resource, scan, and provider const expandedFindings = findingsData?.data From 73c5764495dea1b49bd80cdc79f9ff56fab2e77f Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Tue, 19 Nov 2024 13:05:22 +0100 Subject: [PATCH 393/411] chore: add new component for finding status and add sorting to the findings table --- components/filters/custom-select-provider.tsx | 6 +- components/findings/table/column-findings.tsx | 73 +++++++++++-------- components/ui/table/index.ts | 1 + components/ui/table/severity-badge.tsx | 3 +- components/ui/table/status-finding-badge.tsx | 37 ++++++++++ 5 files changed, 85 insertions(+), 35 deletions(-) create mode 100644 components/ui/table/status-finding-badge.tsx diff --git a/components/filters/custom-select-provider.tsx b/components/filters/custom-select-provider.tsx index 0aff523282..d2a419bbbb 100644 --- a/components/filters/custom-select-provider.tsx +++ b/components/filters/custom-select-provider.tsx @@ -42,16 +42,16 @@ export const CustomSelectProvider: React.FC = () => { (value: string) => { const params = new URLSearchParams(searchParams.toString()); if (value) { - params.set("filter[provider__in]", value); + params.set("filter[provider_type]", value); } else { - params.delete("filter[provider__in]"); + params.delete("filter[provider_type]"); } router.push(`?${params.toString()}`, { scroll: false }); }, [router, searchParams], ); - const currentProvider = searchParams.get("filter[provider__in]") || ""; + const currentProvider = searchParams.get("filter[provider_type]") || ""; const selectedKeys = useMemo(() => { return dataInputsProvider.some( diff --git a/components/findings/table/column-findings.tsx b/components/findings/table/column-findings.tsx index 4d0d7dd3ab..8bfb34998e 100644 --- a/components/findings/table/column-findings.tsx +++ b/components/findings/table/column-findings.tsx @@ -5,19 +5,17 @@ import { ColumnDef } from "@tanstack/react-table"; import { DataTableRowDetails } from "@/components/findings/table"; import { PlusIcon } from "@/components/icons"; import { TriggerSheet } from "@/components/ui/sheet"; -import { SeverityBadge, Status, StatusBadge } from "@/components/ui/table"; +import { + DataTableColumnHeader, + SeverityBadge, + StatusFindingBadge, +} from "@/components/ui/table"; import { FindingProps } from "@/types"; import { DataTableRowActions } from "./data-table-row-actions"; -const statusMap: Record<"PASS" | "FAIL" | "MANUAL" | "MUTED", Status> = { - PASS: "completed", - FAIL: "failed", - MANUAL: "completed", - MUTED: "cancelled", -}; - const getFindingsData = (row: { original: FindingProps }) => { + console.log(row.original, "finding"); return row.original; }; @@ -29,6 +27,7 @@ const getResourceData = ( row: { original: FindingProps }, field: keyof FindingProps["relationships"]["resource"]["attributes"], ) => { + // console.log(row.original, "resource"); return ( row.original.relationships?.resource?.attributes?.[field] || `No ${field} found in resource` @@ -39,6 +38,7 @@ const getProviderData = ( row: { original: FindingProps }, field: keyof FindingProps["relationships"]["provider"]["attributes"], ) => { + // console.log(row.original, "provider"); return ( row.original.relationships?.provider?.attributes?.[field] || `No ${field} found in provider` @@ -49,6 +49,7 @@ const getScanData = ( row: { original: FindingProps }, field: keyof FindingProps["relationships"]["scan"]["attributes"], ) => { + // console.log(row.original, "scan"); return ( row.original.relationships?.scan?.attributes?.[field] || `No ${field} found in scan` @@ -58,30 +59,23 @@ const getScanData = ( export const ColumnFindings: ColumnDef[] = [ { accessorKey: "check", - header: "Check", + header: ({ column }) => ( + + ), cell: ({ row }) => { const { checktitle } = getFindingsMetadata(row); - return

{checktitle}

; - }, - }, - { - accessorKey: "scanName", - header: "Scan Name", - cell: ({ row }) => { - const name = getScanData(row, "name"); - - return ( -

- {typeof name === "string" || typeof name === "number" - ? name - : "Invalid data"} -

- ); + return

{checktitle}

; }, }, { accessorKey: "severity", - header: "Severity", + header: ({ column }) => ( + + ), cell: ({ row }) => { const { attributes: { severity }, @@ -91,15 +85,30 @@ export const ColumnFindings: ColumnDef[] = [ }, { accessorKey: "status", - header: "Status", + header: ({ column }) => ( + + ), cell: ({ row }) => { const { attributes: { status }, } = getFindingsData(row); - const mappedStatus = statusMap[status]; + return ; + }, + }, + { + accessorKey: "scanName", + header: "Scan Name", + cell: ({ row }) => { + const name = getScanData(row, "name"); - return ; + return ( +

+ {typeof name === "string" || typeof name === "number" + ? name + : "Invalid data"} +

+ ); }, }, { @@ -120,7 +129,7 @@ export const ColumnFindings: ColumnDef[] = [ header: "Service", cell: ({ row }) => { const { servicename } = getFindingsMetadata(row); - return

{servicename}

; + return

{servicename}

; }, }, { @@ -131,7 +140,9 @@ export const ColumnFindings: ColumnDef[] = [ return ( <> -
{typeof account === "string" ? account : "Invalid account"}
+

+ {typeof account === "string" ? account : "Invalid account"} +

); }, diff --git a/components/ui/table/index.ts b/components/ui/table/index.ts index a344b959de..48080e8ef7 100644 --- a/components/ui/table/index.ts +++ b/components/ui/table/index.ts @@ -4,4 +4,5 @@ export * from "./data-table-filter-custom"; export * from "./data-table-pagination"; export * from "./severity-badge"; export * from "./status-badge"; +export * from "./status-finding-badge"; export * from "./table"; diff --git a/components/ui/table/severity-badge.tsx b/components/ui/table/severity-badge.tsx index 8b852eea36..d15c8a098a 100644 --- a/components/ui/table/severity-badge.tsx +++ b/components/ui/table/severity-badge.tsx @@ -37,7 +37,8 @@ export const SeverityBadge = ({ severity }: { severity: Severity }) => { return ( = { + FAIL: "danger", + PASS: "success", + MANUAL: "warning", + MUTED: "default", +}; + +export const StatusFindingBadge = ({ + status, + size = "sm", + ...props +}: { + status: FindingStatus; + size?: "sm" | "md" | "lg"; +}) => { + const color = statusColorMap[status]; + + return ( + + {status} + + ); +}; From b28cfede8c057f57356b9cf9bcb9dcc82b6cd36a Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Tue, 19 Nov 2024 17:36:56 +0100 Subject: [PATCH 394/411] chore: remove container class and style tweaks for status finding badge --- app/(prowler)/compliance/page.tsx | 2 +- app/(prowler)/layout.tsx | 2 +- components/findings/table/column-findings.tsx | 30 +++++++++++-------- components/ui/sheet/trigger-sheet.tsx | 2 +- components/ui/table/status-finding-badge.tsx | 2 +- 5 files changed, 22 insertions(+), 16 deletions(-) diff --git a/app/(prowler)/compliance/page.tsx b/app/(prowler)/compliance/page.tsx index 08368c69da..af2652cc66 100644 --- a/app/(prowler)/compliance/page.tsx +++ b/app/(prowler)/compliance/page.tsx @@ -92,7 +92,7 @@ const SSRComplianceGrid = async ({ } return ( -
+
{compliancesData.data.map((compliance: ComplianceOverviewData) => { const { attributes } = compliance; const { diff --git a/app/(prowler)/layout.tsx b/app/(prowler)/layout.tsx index ea8d1f13bc..40a2fd9bf9 100644 --- a/app/(prowler)/layout.tsx +++ b/app/(prowler)/layout.tsx @@ -46,7 +46,7 @@ export default function RootLayout({
-
+
{children}
diff --git a/components/findings/table/column-findings.tsx b/components/findings/table/column-findings.tsx index 8bfb34998e..d5c06862db 100644 --- a/components/findings/table/column-findings.tsx +++ b/components/findings/table/column-findings.tsx @@ -1,9 +1,10 @@ "use client"; import { ColumnDef } from "@tanstack/react-table"; +import { useSearchParams } from "next/navigation"; import { DataTableRowDetails } from "@/components/findings/table"; -import { PlusIcon } from "@/components/icons"; +import { InfoIcon } from "@/components/icons"; import { TriggerSheet } from "@/components/ui/sheet"; import { DataTableColumnHeader, @@ -15,7 +16,6 @@ import { FindingProps } from "@/types"; import { DataTableRowActions } from "./data-table-row-actions"; const getFindingsData = (row: { original: FindingProps }) => { - console.log(row.original, "finding"); return row.original; }; @@ -27,7 +27,6 @@ const getResourceData = ( row: { original: FindingProps }, field: keyof FindingProps["relationships"]["resource"]["attributes"], ) => { - // console.log(row.original, "resource"); return ( row.original.relationships?.resource?.attributes?.[field] || `No ${field} found in resource` @@ -38,7 +37,6 @@ const getProviderData = ( row: { original: FindingProps }, field: keyof FindingProps["relationships"]["provider"]["attributes"], ) => { - // console.log(row.original, "provider"); return ( row.original.relationships?.provider?.attributes?.[field] || `No ${field} found in provider` @@ -49,7 +47,6 @@ const getScanData = ( row: { original: FindingProps }, field: keyof FindingProps["relationships"]["scan"]["attributes"], ) => { - // console.log(row.original, "scan"); return ( row.original.relationships?.scan?.attributes?.[field] || `No ${field} found in scan` @@ -151,14 +148,23 @@ export const ColumnFindings: ColumnDef[] = [ id: "moreInfo", header: "Details", cell: ({ row }) => { + const searchParams = useSearchParams(); + const findingId = searchParams.get("id"); + const isOpen = findingId === row.original.id; return ( - } - title="Finding Details" - description="View the finding details" - > - - +
+ } + title="Finding Details" + description="View the finding details" + defaultOpen={isOpen} + > + + +
); }, }, diff --git a/components/ui/sheet/trigger-sheet.tsx b/components/ui/sheet/trigger-sheet.tsx index 992b3ac098..232173d2d4 100644 --- a/components/ui/sheet/trigger-sheet.tsx +++ b/components/ui/sheet/trigger-sheet.tsx @@ -27,7 +27,7 @@ export function TriggerSheet({ {triggerComponent} - + {title} {description} diff --git a/components/ui/table/status-finding-badge.tsx b/components/ui/table/status-finding-badge.tsx index 277db680dd..3dfc6e2613 100644 --- a/components/ui/table/status-finding-badge.tsx +++ b/components/ui/table/status-finding-badge.tsx @@ -25,7 +25,7 @@ export const StatusFindingBadge = ({ return ( Date: Wed, 20 Nov 2024 08:31:29 +0100 Subject: [PATCH 395/411] chore: finding details tweaks --- components/filters/custom-date-picker.tsx | 2 +- .../findings/table/data-table-row-details.tsx | 40 ++- components/findings/table/finding-detail.tsx | 308 ++++++++++++------ components/invitations/invitation-details.tsx | 2 +- .../workflow/workflow-send-invite.tsx | 2 +- .../workflow/workflow-add-provider.tsx | 2 +- components/scans/table/scan-detail.tsx | 89 ++--- components/ui/custom/custom-button.tsx | 3 +- components/ui/entities/date-with-time.tsx | 6 +- components/ui/sheet/sheet.tsx | 4 +- components/ui/sheet/trigger-sheet.tsx | 2 +- components/ui/sidebar/sidebar.tsx | 2 +- components/ui/table/data-table-pagination.tsx | 8 +- components/ui/table/data-table.tsx | 2 +- components/ui/table/table.tsx | 2 +- tailwind.config.js | 1 + 16 files changed, 310 insertions(+), 165 deletions(-) diff --git a/components/filters/custom-date-picker.tsx b/components/filters/custom-date-picker.tsx index 91118322af..2a982c1781 100644 --- a/components/filters/custom-date-picker.tsx +++ b/components/filters/custom-date-picker.tsx @@ -63,7 +63,7 @@ export const CustomDatePicker = () => { CalendarTopContent={ { - return ; +import { FindingProps } from "@/types/components"; + +import { FindingDetail } from "./finding-detail"; + +export const DataTableRowDetails = ({ + // entityId, + findingDetails, +}: { + entityId: string; + findingDetails: FindingProps; +}) => { + // const router = useRouter(); + // const pathname = usePathname(); + // const searchParams = useSearchParams(); + + // useEffect(() => { + // if (entityId) { + // const params = new URLSearchParams(searchParams.toString()); + // params.set("id", entityId); + // router.push(`${pathname}?${params.toString()}`, { scroll: false }); + // } + + // return () => { + // if (entityId) { + // const cleanupParams = new URLSearchParams(searchParams.toString()); + // cleanupParams.delete("id"); + // router.push(`${pathname}?${cleanupParams.toString()}`, { + // scroll: false, + // }); + // } + // }; + // }, [entityId, pathname, router, searchParams]); + + return ; }; diff --git a/components/findings/table/finding-detail.tsx b/components/findings/table/finding-detail.tsx index 4d2dfac867..b5865c8c73 100644 --- a/components/findings/table/finding-detail.tsx +++ b/components/findings/table/finding-detail.tsx @@ -1,14 +1,11 @@ "use client"; -import { - Table, - TableBody, - TableCell, - TableColumn, - TableHeader, - TableRow, -} from "@nextui-org/react"; +import { Snippet } from "@nextui-org/react"; +import Link from "next/link"; +import { SnippetId } from "@/components/ui/entities"; +import { DateWithTime } from "@/components/ui/entities/date-with-time"; +import { SeverityBadge } from "@/components/ui/table/severity-badge"; import { FindingProps } from "@/types"; export const FindingDetail = ({ @@ -17,108 +14,203 @@ export const FindingDetail = ({ findingDetails: FindingProps; }) => { const finding = findingDetails; + const attributes = finding.attributes; + const resource = finding.relationships.resource.attributes; + + const remediation = attributes.check_metadata.remediation; + return ( -
+
+ {/* Header */}
- - - Name - Value - - - - Resource ID - {finding.relationships.resource.id} - - - Resource ARN - - {finding.relationships.resource.attributes.uid} - - - - Check ID - {finding.attributes.check_id} - - - Types - - {finding.attributes.check_metadata.checktype} - - - - Scan time - {finding.attributes.inserted_at} - - - Prowler Finding ID - - {finding.relationships.resource.attributes.uid} - - - - Severity - {finding.id} - - - Status - {finding.attributes.status} - - - Region - - {finding.relationships.resource.attributes.region} - - - - Service - - {finding.relationships.resource.attributes.service} - - - - Account - - {finding.relationships.provider.attributes.uid} - - - - Details - {finding.attributes.status_extended} - - - Risk - {finding.attributes.check_metadata.risk} - - - Recommendation - - { - finding.attributes.check_metadata.remediation.recommendation - .text - } - - - - CLI - - {finding.attributes.check_metadata.remediation.code.cli} - - - - Other - - {finding.attributes.check_metadata.remediation.code.other} - - - - Terraform - - {finding.attributes.check_metadata.remediation.code.terraform} - - - -
+
+

+ {attributes.check_metadata.checktitle} +

+

+ {resource.service} +

+
+
+ {attributes.status} +
+
+ + {/* Check Metadata */} +
+
+

+ Check Metadata +

+ +
+ {attributes.status === "FAIL" && ( + +

+ Risk +

+

+ {attributes.check_metadata.risk} +

+
+ )} + +
+

+ Description +

+

+ {attributes.check_metadata.description} +

+
+ +
+

+ Remediation +

+
+ {remediation.recommendation && ( + <> +

Recommendation:

+

{remediation.recommendation.text}

+ + Learn more + + + )} + {remediation.code && + Object.values(remediation.code).some(Boolean) && ( + <> +

+ Check these links: +

+
+ {remediation.code.cli && ( +
+

CLI Command:

+ +

+ {remediation.code.cli} +

+
+
+ )} + {Object.entries(remediation.code) + .filter(([key]) => key !== "cli") + .map(([key, value]) => + value ? ( + + {key === "other" + ? "External doc" + : key.charAt(0).toUpperCase() + + key.slice(1).toLowerCase()} + + ) : null, + )} +
+ + )} +
+
+
+ + {/* Resources Section */} +
+

+ Resource Details +

+
+
+

+ Resource ID +

+ +

{resource.uid}

+
+
+
+

+ Resource Name +

+

+ {resource.name} +

+
+
+

+ Region +

+

+ {resource.region} +

+
+
+

+ Resource Type +

+

+ {resource.type} +

+
+
+

+ Severity +

+ +
+ {resource.tags && + Object.entries(resource.tags).map(([key, value]) => ( +
+

+ Tag: {key} +

+ +

{value}

+
+
+ ))} +
+
+

+ Inserted At +

+ +
+
+

+ Updated At +

+ +
+
+
); diff --git a/components/invitations/invitation-details.tsx b/components/invitations/invitation-details.tsx index 7739cf8adb..f335ad6487 100644 --- a/components/invitations/invitation-details.tsx +++ b/components/invitations/invitation-details.tsx @@ -32,7 +32,7 @@ export const InvitationDetails = ({ attributes }: InvitationDetailsProps) => {
diff --git a/components/invitations/workflow/workflow-send-invite.tsx b/components/invitations/workflow/workflow-send-invite.tsx index cf87d04f06..9cbe848093 100644 --- a/components/invitations/workflow/workflow-send-invite.tsx +++ b/components/invitations/workflow/workflow-send-invite.tsx @@ -55,7 +55,7 @@ export const WorkflowSendInvite = () => { diff --git a/components/providers/workflow/workflow-add-provider.tsx b/components/providers/workflow/workflow-add-provider.tsx index 69bc885638..1f2e631f06 100644 --- a/components/providers/workflow/workflow-add-provider.tsx +++ b/components/providers/workflow/workflow-add-provider.tsx @@ -67,7 +67,7 @@ export const WorkflowAddProvider = () => { diff --git a/components/scans/table/scan-detail.tsx b/components/scans/table/scan-detail.tsx index b0a85f73f3..d6d9fedf7b 100644 --- a/components/scans/table/scan-detail.tsx +++ b/components/scans/table/scan-detail.tsx @@ -17,22 +17,25 @@ export const ScanDetail = ({ scanDetails }: ScanDetailsProps) => { const taskDetails = scanDetails.taskDetails; return ( -
+
+ {/* Header */}
-
-

Scan Details

-
- +

+ Scan Details +

- -
-
-
+ + + + {/* Details Section */} +
+
+
{ value={`${scanOnDemand.duration} seconds`} />
-
+
{ dateTime={scanOnDemand.completed_at.toString()} /> ) : ( - "Not Started" + "Not Completed" ) } /> @@ -109,17 +112,21 @@ export const ScanDetail = ({ scanDetails }: ScanDetailsProps) => {
- - -

Scan arguments

+ + {/* Scan Arguments Section */} + + +

+ Scan Arguments +

- - - - + +
- Checks - + + Checks + + {(scanOnDemand.scanner_args as any)?.checks_to_execute?.join( ", ", ) || "N/A"} @@ -127,25 +134,28 @@ export const ScanDetail = ({ scanDetails }: ScanDetailsProps) => {
+ + {/* Task Details Section */} {taskDetails && ( - - -

State details

+ + +

+ State Details +

- - -
+ + +
- {taskDetails.attributes.result && ( <> {taskDetails.attributes.result.exc_message && ( { )} )} -
@@ -180,8 +191,10 @@ const DateItem = ({ value: React.ReactNode; }) => (
- {label}: - {value} +

+ {label}: +

+

{value}

); @@ -193,7 +206,9 @@ const DetailItem = ({ value: React.ReactNode; }) => (
- {label}: - {value} +

+ {label}: +

+

{value}

); diff --git a/components/ui/custom/custom-button.tsx b/components/ui/custom/custom-button.tsx index eeffd07783..a5cdf54a14 100644 --- a/components/ui/custom/custom-button.tsx +++ b/components/ui/custom/custom-button.tsx @@ -8,7 +8,8 @@ import { NextUIColors, NextUIVariants } from "@/types"; export const buttonClasses = { base: "px-4 inline-flex items-center justify-center relative z-0 text-center whitespace-nowrap", - primary: "bg-default-100 hover:bg-default-200 text-default-800", + primary: + "bg-default-100 hover:bg-default-200 text-default-800 dark:bg-prowler-blue-800", secondary: "bg-prowler-grey-light dark:bg-prowler-grey-medium text-white", action: "bg-prowler-theme-green font-bold text-prowler-theme-midnight", dashed: diff --git a/components/ui/entities/date-with-time.tsx b/components/ui/entities/date-with-time.tsx index ae89c61985..05a00b1b3a 100644 --- a/components/ui/entities/date-with-time.tsx +++ b/components/ui/entities/date-with-time.tsx @@ -4,11 +4,13 @@ import React from "react"; interface DateWithTimeProps { dateTime: string | null; // e.g., "2024-07-17T09:55:14.191475Z" showTime?: boolean; + inline?: boolean; } export const DateWithTime: React.FC = ({ dateTime, showTime = true, + inline = false, }) => { if (!dateTime) return --; const date = parseISO(dateTime); @@ -17,7 +19,9 @@ export const DateWithTime: React.FC = ({ return (
-
+
{formattedDate} {showTime && ( {formattedTime} diff --git a/components/ui/sheet/sheet.tsx b/components/ui/sheet/sheet.tsx index cf945c0f9d..d1fc590d84 100644 --- a/components/ui/sheet/sheet.tsx +++ b/components/ui/sheet/sheet.tsx @@ -31,7 +31,7 @@ const SheetOverlay = React.forwardRef< SheetOverlay.displayName = SheetPrimitive.Overlay.displayName; const sheetVariants = cva( - "fixed z-50 gap-4 bg-white p-6 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500 data-[state=open]:animate-in data-[state=closed]:animate-out dark:bg-neutral-950", + "fixed z-50 gap-4 bg-white p-6 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500 data-[state=open]:animate-in data-[state=closed]:animate-out dark:bg-neutral-950 dark:border-prowler-blue-800", { variants: { side: { @@ -40,7 +40,7 @@ const sheetVariants = cva( "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom", left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left", right: - "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right", + "inset-y-0 right-0 h-full w-3/4 border-t-1 border-b-1 border-l-2 data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right", }, }, defaultVariants: { diff --git a/components/ui/sheet/trigger-sheet.tsx b/components/ui/sheet/trigger-sheet.tsx index 232173d2d4..5d5fc45ce6 100644 --- a/components/ui/sheet/trigger-sheet.tsx +++ b/components/ui/sheet/trigger-sheet.tsx @@ -27,7 +27,7 @@ export function TriggerSheet({ {triggerComponent} - + {title} {description} diff --git a/components/ui/sidebar/sidebar.tsx b/components/ui/sidebar/sidebar.tsx index 23a58abc79..63feff298e 100644 --- a/components/ui/sidebar/sidebar.tsx +++ b/components/ui/sidebar/sidebar.tsx @@ -280,7 +280,7 @@ const Sidebar = React.forwardRef( itemClasses={{ ...itemClasses, base: clsx( - "px-3 rounded-large data-[selected=true]:bg-default-100", + "px-3 rounded-large data-[selected=true]:bg-default-100 dark:data-[selected=true]:bg-prowler-blue-800", itemClasses?.base, ), title: clsx( diff --git a/components/ui/table/data-table-pagination.tsx b/components/ui/table/data-table-pagination.tsx index e17170d7c8..dfcec441a3 100644 --- a/components/ui/table/data-table-pagination.tsx +++ b/components/ui/table/data-table-pagination.tsx @@ -49,7 +49,7 @@ export function DataTablePagination({ metadata }: DataTablePaginationProps) {
@@ -57,7 +57,7 @@ export function DataTablePagination({ metadata }: DataTablePaginationProps) { @@ -65,14 +65,14 @@ export function DataTablePagination({ metadata }: DataTablePaginationProps) {
)} -
+
{table.getHeaderGroups().map((headerGroup) => ( diff --git a/components/ui/table/table.tsx b/components/ui/table/table.tsx index aca884960c..bd04b9daf3 100644 --- a/components/ui/table/table.tsx +++ b/components/ui/table/table.tsx @@ -75,7 +75,7 @@ const TableHead = React.forwardRef<
Date: Wed, 20 Nov 2024 09:46:04 +0100 Subject: [PATCH 396/411] chore: color tweaks --- components/findings/table/data-table-row-actions.tsx | 5 ++++- components/invitations/table/data-table-row-actions.tsx | 5 ++++- components/providers/table/data-table-row-actions.tsx | 5 ++++- components/scans/table/scans/data-table-row-actions.tsx | 5 ++++- components/ui/custom/custom-alert-modal.tsx | 1 + components/ui/table/data-table.tsx | 2 +- components/users/table/data-table-row-actions.tsx | 5 ++++- 7 files changed, 22 insertions(+), 6 deletions(-) diff --git a/components/findings/table/data-table-row-actions.tsx b/components/findings/table/data-table-row-actions.tsx index f96f804fc9..287e268ffb 100644 --- a/components/findings/table/data-table-row-actions.tsx +++ b/components/findings/table/data-table-row-actions.tsx @@ -56,7 +56,10 @@ export function DataTableRowActions({ */}
- +
)} -
+
{table.getHeaderGroups().map((headerGroup) => ( diff --git a/components/users/table/data-table-row-actions.tsx b/components/users/table/data-table-row-actions.tsx index 07a00cd6a7..61f4cdc390 100644 --- a/components/users/table/data-table-row-actions.tsx +++ b/components/users/table/data-table-row-actions.tsx @@ -62,7 +62,10 @@ export function DataTableRowActions({
- +
- + )} From 07beb094fb1065a6005cbf43f46a18d955fc6678 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Wed, 20 Nov 2024 09:57:31 +0100 Subject: [PATCH 398/411] chore:color tweaks --- components/ui/custom/custom-dropdown-filter.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/ui/custom/custom-dropdown-filter.tsx b/components/ui/custom/custom-dropdown-filter.tsx index d333545f49..f05e473d6a 100644 --- a/components/ui/custom/custom-dropdown-filter.tsx +++ b/components/ui/custom/custom-dropdown-filter.tsx @@ -97,7 +97,7 @@ export const CustomDropdownFilter: React.FC = ({ - +
Date: Wed, 20 Nov 2024 10:15:14 +0100 Subject: [PATCH 399/411] chore: move filters outside of the table --- app/(prowler)/findings/page.tsx | 7 ++++--- app/(prowler)/invitations/page.tsx | 8 ++++---- app/(prowler)/providers/page.tsx | 8 ++++---- app/(prowler)/scans/page.tsx | 5 +++-- app/(prowler)/users/page.tsx | 8 ++++---- components/ui/table/data-table.tsx | 7 ------- 6 files changed, 19 insertions(+), 24 deletions(-) diff --git a/app/(prowler)/findings/page.tsx b/app/(prowler)/findings/page.tsx index bbfdde2198..505a614a6b 100644 --- a/app/(prowler)/findings/page.tsx +++ b/app/(prowler)/findings/page.tsx @@ -9,7 +9,7 @@ import { SkeletonTableFindings, } from "@/components/findings/table"; import { Header } from "@/components/ui"; -import { DataTable } from "@/components/ui/table"; +import { DataTable, DataTableFilterCustom } from "@/components/ui/table"; import { createDict } from "@/lib"; import { FindingProps, SearchParamsProps } from "@/types/components"; @@ -26,7 +26,9 @@ export default async function Findings({ - + + + }> @@ -84,7 +86,6 @@ const SSRDataTable = async ({ columns={ColumnFindings} data={expandedResponse?.data || []} metadata={findingsData?.meta} - customFilters={filterFindings} /> ); }; diff --git a/app/(prowler)/invitations/page.tsx b/app/(prowler)/invitations/page.tsx index b32573cd45..f444aa474a 100644 --- a/app/(prowler)/invitations/page.tsx +++ b/app/(prowler)/invitations/page.tsx @@ -10,7 +10,7 @@ import { SkeletonTableInvitation, } from "@/components/invitations/table"; import { Header } from "@/components/ui"; -import { DataTable } from "@/components/ui/table"; +import { DataTable, DataTableFilterCustom } from "@/components/ui/table"; import { SearchParamsProps } from "@/types"; export default async function Invitations({ @@ -25,9 +25,10 @@ export default async function Invitations({
- + - + + }> @@ -59,7 +60,6 @@ const SSRDataTable = async ({ columns={ColumnsInvitation} data={invitationsData?.data || []} metadata={invitationsData?.meta} - customFilters={filterInvitations} /> ); }; diff --git a/app/(prowler)/providers/page.tsx b/app/(prowler)/providers/page.tsx index 5b44c3d2d1..03c8ea8edf 100644 --- a/app/(prowler)/providers/page.tsx +++ b/app/(prowler)/providers/page.tsx @@ -9,7 +9,7 @@ import { SkeletonTableProviders, } from "@/components/providers/table"; import { Header } from "@/components/ui"; -import { DataTable } from "@/components/ui/table"; +import { DataTable, DataTableFilterCustom } from "@/components/ui/table"; import { SearchParamsProps } from "@/types"; export default async function Providers({ @@ -25,9 +25,10 @@ export default async function Providers({ - + - + + }> @@ -58,7 +59,6 @@ const SSRDataTable = async ({ columns={ColumnProviders} data={providersData?.data || []} metadata={providersData?.meta} - customFilters={filterProviders} /> ); }; diff --git a/app/(prowler)/scans/page.tsx b/app/(prowler)/scans/page.tsx index 4ea3db2b76..1c251114cb 100644 --- a/app/(prowler)/scans/page.tsx +++ b/app/(prowler)/scans/page.tsx @@ -8,7 +8,7 @@ import { LaunchScanWorkflow } from "@/components/scans/launch-workflow"; import { SkeletonTableScans } from "@/components/scans/table"; import { ColumnGetScans } from "@/components/scans/table/scans"; import { Header } from "@/components/ui"; -import { DataTable } from "@/components/ui/table"; +import { DataTable, DataTableFilterCustom } from "@/components/ui/table"; import { ProviderProps, SearchParamsProps } from "@/types"; export default async function Scans({ @@ -39,6 +39,8 @@ export default async function Scans({ + +
@@ -76,7 +78,6 @@ const SSRDataTableScans = async ({ columns={ColumnGetScans} data={scansData?.data || []} metadata={scansData?.meta} - customFilters={filterScans} /> ); }; diff --git a/app/(prowler)/users/page.tsx b/app/(prowler)/users/page.tsx index 805c8271e6..853bf2209e 100644 --- a/app/(prowler)/users/page.tsx +++ b/app/(prowler)/users/page.tsx @@ -5,7 +5,7 @@ import { getUsers } from "@/actions/users/users"; import { FilterControls } from "@/components/filters"; import { filterUsers } from "@/components/filters/data-filters"; import { Header } from "@/components/ui"; -import { DataTable } from "@/components/ui/table"; +import { DataTable, DataTableFilterCustom } from "@/components/ui/table"; import { AddUserButton } from "@/components/users"; import { ColumnsUser, SkeletonTableUser } from "@/components/users/table"; import { SearchParamsProps } from "@/types"; @@ -22,9 +22,10 @@ export default async function Users({
- + - + + }> @@ -56,7 +57,6 @@ const SSRDataTable = async ({ columns={ColumnsUser} data={usersData?.data || []} metadata={usersData?.meta} - customFilters={filterUsers} /> ); }; diff --git a/components/ui/table/data-table.tsx b/components/ui/table/data-table.tsx index f1ddd28072..ad5a866a01 100644 --- a/components/ui/table/data-table.tsx +++ b/components/ui/table/data-table.tsx @@ -14,7 +14,6 @@ import { import { useState } from "react"; import { - DataTableFilterCustom, Table, TableBody, TableCell, @@ -36,7 +35,6 @@ export function DataTable({ columns, data, metadata, - customFilters, }: DataTableProviderProps) { const [sorting, setSorting] = useState([]); const [columnFilters, setColumnFilters] = useState([]); @@ -58,11 +56,6 @@ export function DataTable({ return ( <> - {customFilters && ( -
- -
- )}
From ebc96bed06509a7ad01353ce1e026b120d1d7c32 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Wed, 20 Nov 2024 10:16:56 +0100 Subject: [PATCH 400/411] chore: spacing tweaks --- app/(prowler)/invitations/page.tsx | 1 + app/(prowler)/providers/page.tsx | 1 + app/(prowler)/users/page.tsx | 1 + 3 files changed, 3 insertions(+) diff --git a/app/(prowler)/invitations/page.tsx b/app/(prowler)/invitations/page.tsx index f444aa474a..b18cbbcba3 100644 --- a/app/(prowler)/invitations/page.tsx +++ b/app/(prowler)/invitations/page.tsx @@ -27,6 +27,7 @@ export default async function Invitations({ + diff --git a/app/(prowler)/providers/page.tsx b/app/(prowler)/providers/page.tsx index 03c8ea8edf..7aae9df08d 100644 --- a/app/(prowler)/providers/page.tsx +++ b/app/(prowler)/providers/page.tsx @@ -27,6 +27,7 @@ export default async function Providers({ + diff --git a/app/(prowler)/users/page.tsx b/app/(prowler)/users/page.tsx index 853bf2209e..364651ca22 100644 --- a/app/(prowler)/users/page.tsx +++ b/app/(prowler)/users/page.tsx @@ -24,6 +24,7 @@ export default async function Users({ + From 7fd53c1bc3462bf2c8fe3add23cc3efb55804542 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Wed, 20 Nov 2024 13:58:45 +0100 Subject: [PATCH 401/411] feat: tweaks filters --- app/(prowler)/findings/page.tsx | 86 ++++++++++++++++++- components/compliance/compliance-card.tsx | 32 ++++--- components/filters/data-filters.ts | 11 ++- .../findings/table/data-table-row-actions.tsx | 2 +- .../ui/custom/custom-dropdown-filter.tsx | 6 +- .../ui/table/data-table-filter-custom.tsx | 23 +++-- 6 files changed, 129 insertions(+), 31 deletions(-) diff --git a/app/(prowler)/findings/page.tsx b/app/(prowler)/findings/page.tsx index 505a614a6b..c1da5ffbb9 100644 --- a/app/(prowler)/findings/page.tsx +++ b/app/(prowler)/findings/page.tsx @@ -2,6 +2,8 @@ import { Spacer } from "@nextui-org/react"; import React, { Suspense } from "react"; import { getFindings } from "@/actions/findings"; +import { getProviders } from "@/actions/providers"; +import { getScans } from "@/actions/scans"; import { filterFindings } from "@/components/filters/data-filters"; import { FilterControls } from "@/components/filters/filter-controls"; import { @@ -20,14 +22,94 @@ export default async function Findings({ }) { const searchParamsKey = JSON.stringify(searchParams || {}); + // Get findings data + const findingsData = await getFindings({}); + const providersData = await getProviders({}); + const scansData = await getScans({}); + + // Extract provider UIDs + const providerUIDs = providersData?.data + ?.map((provider: any) => provider.attributes.uid) + .filter(Boolean); + + // Extract scan UUIDs with "completed" state and more than one resource + const completedScans = scansData?.data + ?.filter( + (scan: any) => + scan.attributes.state === "completed" && + scan.attributes.unique_resource_count > 1 && + scan.attributes.name, // Ensure it has a name + ) + .map((scan: any) => ({ + id: scan.id, + name: scan.attributes.name, + })); + + const completedScanIds = completedScans?.map((scan: any) => scan.id) || []; + + // Create resource dictionary + const resourceDict = createDict("resources", findingsData); + + // Get unique regions and services + const allRegionsAndServices = findingsData?.data + ?.flatMap((finding: FindingProps) => { + const resource = + resourceDict[finding.relationships?.resources?.data?.[0]?.id]; + return { + region: resource?.attributes?.region, + service: resource?.attributes?.service, + }; + }) + .filter(Boolean); + + const uniqueRegions = Array.from( + new Set( + allRegionsAndServices + .map((item: { region: string }) => item.region) + .filter(Boolean), + ), + ); + const uniqueServices = Array.from( + new Set( + allRegionsAndServices + .map((item: { service: string }) => item.service) + .filter(Boolean), + ), + ); + return ( <>
- + - + }> diff --git a/components/compliance/compliance-card.tsx b/components/compliance/compliance-card.tsx index 41cd740a73..7fcfdbf5dd 100644 --- a/components/compliance/compliance-card.tsx +++ b/components/compliance/compliance-card.tsx @@ -16,27 +16,25 @@ export const ComplianceCard: React.FC = ({ title, passingRequirements, totalRequirements, - prevPassingRequirements, - prevTotalRequirements, }) => { const ratingPercentage = Math.floor( (passingRequirements / totalRequirements) * 100, ); - const prevRatingPercentage = Math.floor( - (prevPassingRequirements / prevTotalRequirements) * 100, - ); + // const prevRatingPercentage = Math.floor( + // (prevPassingRequirements / prevTotalRequirements) * 100, + // ); - const getScanChange = () => { - const scanDifference = ratingPercentage - prevRatingPercentage; - if (scanDifference < 0 && scanDifference <= -1) { - return `${scanDifference}% from last scan`; - } - if (scanDifference > 0 && scanDifference >= 1) { - return `+${scanDifference}% from last scan`; - } - return "No change from last scan"; - }; + // const getScanChange = () => { + // const scanDifference = ratingPercentage - prevRatingPercentage; + // if (scanDifference < 0 && scanDifference <= -1) { + // return `${scanDifference}% from last scan`; + // } + // if (scanDifference > 0 && scanDifference >= 1) { + // return `+${scanDifference}% from last scan`; + // } + // return "No changes from last scan"; + // }; const getRatingColor = (ratingPercentage: number) => { if (ratingPercentage <= 10) { @@ -50,7 +48,7 @@ export const ComplianceCard: React.FC = ({ return ( - +
= ({ Passing Requirements - {getScanChange()} + {/* {getScanChange()} */}
diff --git a/components/filters/data-filters.ts b/components/filters/data-filters.ts index 91a039605d..e396791b64 100644 --- a/components/filters/data-filters.ts +++ b/components/filters/data-filters.ts @@ -35,20 +35,25 @@ export const filterScans = [ export const filterFindings = [ { - key: "severity", + key: "severity__in", labelCheckboxGroup: "Severity", values: ["critical", "high", "medium", "low", "informational"], }, { - key: "status", + key: "status__in", labelCheckboxGroup: "Status", values: ["PASS", "FAIL", "MANUAL", "MUTED"], }, { - key: "delta", + key: "delta__in", labelCheckboxGroup: "Delta", values: ["new", "changed"], }, + { + key: "provider_type__in", + labelCheckboxGroup: "Provider", + values: ["aws", "azure", "gcp", "kubernetes"], + }, // Add more filter categories as needed ]; diff --git a/components/findings/table/data-table-row-actions.tsx b/components/findings/table/data-table-row-actions.tsx index 287e268ffb..8e15c942a0 100644 --- a/components/findings/table/data-table-row-actions.tsx +++ b/components/findings/table/data-table-row-actions.tsx @@ -31,7 +31,6 @@ export function DataTableRowActions({ row, }: DataTableRowActionsProps) { const findingId = (row.original as { id: string }).id; - console.log(findingId); return ( <> {/* ({ startContent={} // onClick={() => setIsEditOpen(true)} > + {findingId} Send to Jira = ({ filter, @@ -97,9 +97,9 @@ export const CustomDropdownFilter: React.FC = ({ )} @@ -149,7 +172,7 @@ export const CustomDropdownFilter: React.FC = ({ > {allFilterKeys.map((value) => ( - {_.capitalize(value)} + {value} ))} From f45edc18a97bb0f95a2ba8f6c3f1b7d3463b1e9c Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Sun, 24 Nov 2024 09:27:18 +0100 Subject: [PATCH 405/411] chore: updating changes with prowler cloud ui --- components/ui/custom/custom-alert-modal.tsx | 8 +++++--- components/ui/table/data-table.tsx | 8 +++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/components/ui/custom/custom-alert-modal.tsx b/components/ui/custom/custom-alert-modal.tsx index bd93c6839b..4da8ba284d 100644 --- a/components/ui/custom/custom-alert-modal.tsx +++ b/components/ui/custom/custom-alert-modal.tsx @@ -4,9 +4,10 @@ import React, { ReactNode } from "react"; interface CustomAlertModalProps { isOpen: boolean; onOpenChange: (isOpen: boolean) => void; - title: string; - description: string; + title?: string; + description?: string; children: ReactNode; + className?: string; } export const CustomAlertModal: React.FC = ({ @@ -15,13 +16,14 @@ export const CustomAlertModal: React.FC = ({ title, description, children, + className, }) => { return ( diff --git a/components/ui/table/data-table.tsx b/components/ui/table/data-table.tsx index ad5a866a01..4577f1bf08 100644 --- a/components/ui/table/data-table.tsx +++ b/components/ui/table/data-table.tsx @@ -106,9 +106,11 @@ export function DataTable({
-
- -
+ {metadata && ( +
+ +
+ )} ); } From 520a5fc756287bd67748e0c5100b9496d1da06d3 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Sun, 24 Nov 2024 10:25:30 +0100 Subject: [PATCH 406/411] chore: changes for setup provider's workflow --- .../workflow/forms/connect-account-form.tsx | 4 +++- .../workflow/forms/launch-scan-form.tsx | 23 ++++++++++++++---- .../workflow/forms/test-connection-form.tsx | 24 ++++++++++++++++--- .../scans/table/scans/column-get-scans.tsx | 4 +++- 4 files changed, 45 insertions(+), 10 deletions(-) diff --git a/components/providers/workflow/forms/connect-account-form.tsx b/components/providers/workflow/forms/connect-account-form.tsx index 9a0825ef66..54acff1491 100644 --- a/components/providers/workflow/forms/connect-account-form.tsx +++ b/components/providers/workflow/forms/connect-account-form.tsx @@ -60,7 +60,9 @@ export const ConnectAccountForm = () => { if (data?.errors && data.errors.length > 0) { data.errors.forEach((error: ApiError) => { const errorMessage = error.detail; - switch (error.source.pointer) { + const pointer = error.source?.pointer; + + switch (pointer) { case "/data/attributes/provider": form.setError("providerType", { type: "server", diff --git a/components/providers/workflow/forms/launch-scan-form.tsx b/components/providers/workflow/forms/launch-scan-form.tsx index 86325d437b..2532fd2e90 100644 --- a/components/providers/workflow/forms/launch-scan-form.tsx +++ b/components/providers/workflow/forms/launch-scan-form.tsx @@ -7,7 +7,7 @@ import { useForm } from "react-hook-form"; import { z } from "zod"; import { scanOnDemand } from "@/actions/scans/scans"; -import { RocketIcon, ScheduleIcon } from "@/components/icons"; +import { AddIcon } from "@/components/icons"; import { CustomButton } from "@/components/ui/custom"; import { Form } from "@/components/ui/form"; import { ProviderProps } from "@/types"; // Asegúrate de importar la interfaz correcta @@ -51,7 +51,7 @@ export const LaunchScanForm = ({ }, }); - const isLoading = form.formState.isSubmitting; + // const isLoading = form.formState.isSubmitting; const onSubmitClient = async (values: FormValues) => { const formData = new FormData(); @@ -94,10 +94,11 @@ export const LaunchScanForm = ({ >
- Launch scan + Scan started
- Launch the scan now or schedule it for a later date and time. + The scan has just started. From now on, a new scan will be launched + every 24 hours, starting from this moment.
@@ -116,7 +117,7 @@ export const LaunchScanForm = ({ -
+ {/*
{isLoading ? <>Loading : Start now} +
*/} +
+ } + > + Go to Scans +
diff --git a/components/providers/workflow/forms/test-connection-form.tsx b/components/providers/workflow/forms/test-connection-form.tsx index 6d7ca16dbe..6431c1a814 100644 --- a/components/providers/workflow/forms/test-connection-form.tsx +++ b/components/providers/workflow/forms/test-connection-form.tsx @@ -12,6 +12,7 @@ import { checkConnectionProvider, deleteCredentials, } from "@/actions/providers"; +import { scanOnDemand } from "@/actions/scans"; import { getTask } from "@/actions/task/tasks"; import { CheckIcon, SaveIcon } from "@/components/icons"; import { useToast } from "@/components/ui"; @@ -115,9 +116,26 @@ export const TestConnectionForm = ({ }); if (connected) { - router.push( - `/providers/launch-scan?type=${providerType}&id=${providerId}`, - ); + try { + const data = await scanOnDemand(formData); + + if (data.error) { + setApiErrorMessage(data.error); + form.setError("providerId", { + type: "server", + message: data.error, + }); + } else { + router.push( + `/providers/launch-scan?type=${providerType}&id=${providerId}`, + ); + } + } catch (error) { + form.setError("providerId", { + type: "server", + message: "An unexpected error occurred. Please try again.", + }); + } } else { setConnectionStatus({ connected: false, diff --git a/components/scans/table/scans/column-get-scans.tsx b/components/scans/table/scans/column-get-scans.tsx index 0d8bfb4360..ed235d2965 100644 --- a/components/scans/table/scans/column-get-scans.tsx +++ b/components/scans/table/scans/column-get-scans.tsx @@ -127,9 +127,11 @@ export const ColumnGetScans: ColumnDef[] = [ const { attributes: { name }, } = getScanData(row); - if (name.length === 0) { + + if (!name || name.length === 0) { return -; } + return {name}; }, }, From c7b463d61e3f98cad75411e769539180e11cf443 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Sun, 24 Nov 2024 11:57:29 +0100 Subject: [PATCH 407/411] chore: refresh scan's data with a button --- app/(prowler)/scans/page.tsx | 23 +++++++++++++++++- components/scans/button-refresh-data.tsx | 30 ++++++++++++++++++++++++ components/scans/index.ts | 1 + components/ui/custom/custom-button.tsx | 2 +- 4 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 components/scans/button-refresh-data.tsx create mode 100644 components/scans/index.ts diff --git a/app/(prowler)/scans/page.tsx b/app/(prowler)/scans/page.tsx index 1c251114cb..5b92328cb8 100644 --- a/app/(prowler)/scans/page.tsx +++ b/app/(prowler)/scans/page.tsx @@ -4,6 +4,7 @@ import { Suspense } from "react"; import { getProviders } from "@/actions/providers"; import { getScans } from "@/actions/scans"; import { filterScans } from "@/components/filters"; +import { ButtonRefreshData } from "@/components/scans"; import { LaunchScanWorkflow } from "@/components/scans/launch-workflow"; import { SkeletonTableScans } from "@/components/scans/table"; import { ColumnGetScans } from "@/components/scans/table/scans"; @@ -32,6 +33,8 @@ export default async function Scans({ })) : []; + // const executingScans = await getExecutingScans(); + return ( <>
@@ -39,7 +42,16 @@ export default async function Scans({ - +
+ + { + "use server"; + await getScans({}); + }} + /> +
+
@@ -81,3 +93,12 @@ const SSRDataTableScans = async ({ /> ); }; + +// const getExecutingScans = async () => { +// const scansData = await getScans({}); + +// return scansData?.data?.some( +// (scan: ScanProps) => +// scan.attributes.state === "executing" && scan.attributes.progress < 100, +// ); +// }; diff --git a/components/scans/button-refresh-data.tsx b/components/scans/button-refresh-data.tsx new file mode 100644 index 0000000000..41d05e3981 --- /dev/null +++ b/components/scans/button-refresh-data.tsx @@ -0,0 +1,30 @@ +"use client"; + +import { RefreshCcwIcon } from "lucide-react"; +import { useTransition } from "react"; + +import { CustomButton } from "../ui/custom"; + +export const ButtonRefreshData = ({ + onPress, +}: { + onPress: () => Promise; +}) => { + const [isPending, startTransition] = useTransition(); + + return ( + } + isLoading={isPending} + onPress={() => { + startTransition(async () => { + await onPress(); + }); + }} + /> + ); +}; diff --git a/components/scans/index.ts b/components/scans/index.ts new file mode 100644 index 0000000000..7d435fad43 --- /dev/null +++ b/components/scans/index.ts @@ -0,0 +1 @@ +export * from "./button-refresh-data"; diff --git a/components/ui/custom/custom-button.tsx b/components/ui/custom/custom-button.tsx index a5cdf54a14..a585ca8274 100644 --- a/components/ui/custom/custom-button.tsx +++ b/components/ui/custom/custom-button.tsx @@ -42,7 +42,7 @@ interface CustomButtonProps { | "danger" | "transparent"; onPress?: (e: PressEvent) => void; - children: React.ReactNode; + children?: React.ReactNode; startContent?: React.ReactNode; endContent?: React.ReactNode; size?: "sm" | "md" | "lg"; From 121b24b7d1e61c5b5d20cf58e16dca44130e805b Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Sun, 24 Nov 2024 13:21:42 +0100 Subject: [PATCH 408/411] chore: improve filtering component --- app/(prowler)/findings/page.tsx | 25 ++++---- .../ui/custom/custom-dropdown-filter.tsx | 58 ++++++++++--------- 2 files changed, 43 insertions(+), 40 deletions(-) diff --git a/app/(prowler)/findings/page.tsx b/app/(prowler)/findings/page.tsx index c1da5ffbb9..53c0795f23 100644 --- a/app/(prowler)/findings/page.tsx +++ b/app/(prowler)/findings/page.tsx @@ -51,29 +51,30 @@ export default async function Findings({ const resourceDict = createDict("resources", findingsData); // Get unique regions and services - const allRegionsAndServices = findingsData?.data - ?.flatMap((finding: FindingProps) => { - const resource = - resourceDict[finding.relationships?.resources?.data?.[0]?.id]; - return { - region: resource?.attributes?.region, - service: resource?.attributes?.service, - }; - }) - .filter(Boolean); + const allRegionsAndServices = + findingsData?.data + ?.flatMap((finding: FindingProps) => { + const resource = + resourceDict[finding.relationships?.resources?.data?.[0]?.id]; + return { + region: resource?.attributes?.region, + service: resource?.attributes?.service, + }; + }) + .filter(Boolean) || []; const uniqueRegions = Array.from( new Set( allRegionsAndServices .map((item: { region: string }) => item.region) - .filter(Boolean), + .filter(Boolean) || [], ), ); const uniqueServices = Array.from( new Set( allRegionsAndServices .map((item: { service: string }) => item.service) - .filter(Boolean), + .filter(Boolean) || [], ), ); diff --git a/components/ui/custom/custom-dropdown-filter.tsx b/components/ui/custom/custom-dropdown-filter.tsx index c1fc5ace9d..da7f2e72a8 100644 --- a/components/ui/custom/custom-dropdown-filter.tsx +++ b/components/ui/custom/custom-dropdown-filter.tsx @@ -44,55 +44,57 @@ export const CustomDropdownFilter: React.FC = ({ return currentFilters; }, [searchParams, filter]); + const memoizedFilterValues = useMemo( + () => filter?.values || [], + [filter?.values], + ); + useEffect(() => { if (filter && getActiveFilter[filter.key]) { const activeValues = getActiveFilter[filter.key].split(","); const newSelection = new Set(activeValues); - if (newSelection.size === allFilterKeys.length) { + if (newSelection.size === memoizedFilterValues.length) { newSelection.add("all"); } setGroupSelected(newSelection); } else { setGroupSelected(new Set()); } - }, [getActiveFilter, filter, allFilterKeys]); + }, [getActiveFilter, filter?.key, memoizedFilterValues]); const onSelectionChange = useCallback( (keys: string[]) => { - const newSelection = new Set(keys); + setGroupSelected((prevGroupSelected) => { + const newSelection = new Set(keys); - if ( - newSelection.size === allFilterKeys.length && - !newSelection.has("all") - ) { - setGroupSelected(new Set(["all", ...allFilterKeys])); - } else if (groupSelected.has("all")) { - newSelection.delete("all"); - const remainingValues = allFilterKeys.filter((key) => - newSelection.has(key), - ); - setGroupSelected(new Set(remainingValues)); - } else { - setGroupSelected(newSelection); - } + if ( + newSelection.size === allFilterKeys.length && + !newSelection.has("all") + ) { + return new Set(["all", ...allFilterKeys]); + } else if (prevGroupSelected.has("all")) { + newSelection.delete("all"); + return new Set(allFilterKeys.filter((key) => newSelection.has(key))); + } + return newSelection; + }); if (onFilterChange && filter) { - const selectedValues = Array.from(newSelection).filter( - (key) => key !== "all", - ); + const selectedValues = keys.filter((key) => key !== "all"); onFilterChange(filter.key, selectedValues); } }, - [allFilterKeys, groupSelected, onFilterChange, filter], + [allFilterKeys, onFilterChange, filter], ); const handleSelectAllClick = useCallback(() => { - if (groupSelected.has("all")) { - setGroupSelected(new Set()); - } else { - setGroupSelected(new Set(["all", ...allFilterKeys])); - } - }, [groupSelected, allFilterKeys]); + setGroupSelected((prevGroupSelected) => { + if (prevGroupSelected.has("all")) { + return new Set(); + } + return new Set(["all", ...allFilterKeys]); + }); + }, [allFilterKeys]); const onClearFilter = useCallback( (filterKey: string) => { @@ -170,7 +172,7 @@ export const CustomDropdownFilter: React.FC = ({ hideScrollBar className="flex max-h-96 max-w-56 flex-col gap-y-2 py-2" > - {allFilterKeys.map((value) => ( + {memoizedFilterValues.map((value) => ( {value} From f8af960909ea172a7b472d6cff9fc571a7284b7c Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Sun, 24 Nov 2024 15:22:12 +0100 Subject: [PATCH 409/411] feat: add graph in overview page with providers overview --- actions/overview/overview.ts | 49 ++++++++ app/(prowler)/page.tsx | 52 ++++---- components/overview/index.ts | 2 + .../provider-overview/provider-overview.tsx | 111 ++++++++++++++++++ .../skeleton-provider-overview.tsx | 64 ++++++++++ types/components.ts | 21 ++++ 6 files changed, 278 insertions(+), 21 deletions(-) create mode 100644 actions/overview/overview.ts create mode 100644 components/overview/provider-overview/provider-overview.tsx create mode 100644 components/overview/provider-overview/skeleton-provider-overview.tsx diff --git a/actions/overview/overview.ts b/actions/overview/overview.ts new file mode 100644 index 0000000000..8d83ab7088 --- /dev/null +++ b/actions/overview/overview.ts @@ -0,0 +1,49 @@ +"use server"; +import { revalidatePath } from "next/cache"; +import { redirect } from "next/navigation"; + +import { auth } from "@/auth.config"; +import { parseStringify } from "@/lib"; + +export const getProvidersOverview = async ({ + page = 1, + query = "", + sort = "", + filters = {}, +}) => { + const session = await auth(); + + if (isNaN(Number(page)) || page < 1) redirect("/providers-overview"); + + const keyServer = process.env.API_BASE_URL; + const url = new URL(`${keyServer}/overviews/providers`); + + if (page) url.searchParams.append("page[number]", page.toString()); + if (query) url.searchParams.append("filter[search]", query); + if (sort) url.searchParams.append("sort", sort); + + // Handle multiple filters + Object.entries(filters).forEach(([key, value]) => { + if (key !== "filter[search]") { + url.searchParams.append(key, String(value)); + } + }); + + try { + const response = await fetch(url.toString(), { + headers: { + Accept: "application/vnd.api+json", + Authorization: `Bearer ${session?.accessToken}`, + }, + }); + + const data = await response.json(); + const parsedData = parseStringify(data); + revalidatePath("/providers-overview"); + return parsedData; + } catch (error) { + // eslint-disable-next-line no-console + console.error("Error fetching providers overview:", error); + return undefined; + } +}; diff --git a/app/(prowler)/page.tsx b/app/(prowler)/page.tsx index 335b53b4f4..bae0d835c4 100644 --- a/app/(prowler)/page.tsx +++ b/app/(prowler)/page.tsx @@ -1,32 +1,42 @@ import { Spacer } from "@nextui-org/react"; +import { Suspense } from "react"; +import { getProvidersOverview } from "@/actions/overview/overview"; +import { + ProvidersOverview, + SkeletonProvidersOverview, +} from "@/components/overview"; import { Header } from "@/components/ui"; export default function Home() { return ( <>
- - {/*
- - - - - - - - - -
*/} + +
+
+ {/* Providers Overview */} +
+ }> + + +
+ +
+ +
+
+
); } + +const SSRProvidersOverview = async () => { + const providersOverview = await getProvidersOverview({}); + + if (!providersOverview) { + return

There is no providers overview info available

; + } + + return ; +}; diff --git a/components/overview/index.ts b/components/overview/index.ts index 452749b068..eb393767f1 100644 --- a/components/overview/index.ts +++ b/components/overview/index.ts @@ -1 +1,3 @@ export * from "./AttackSurface"; +export * from "./provider-overview/provider-overview"; +export * from "./provider-overview/skeleton-provider-overview"; diff --git a/components/overview/provider-overview/provider-overview.tsx b/components/overview/provider-overview/provider-overview.tsx new file mode 100644 index 0000000000..6c5edfb102 --- /dev/null +++ b/components/overview/provider-overview/provider-overview.tsx @@ -0,0 +1,111 @@ +"use client"; + +import { Card, CardBody, CardHeader } from "@nextui-org/react"; + +import { AddIcon } from "@/components/icons/Icons"; +import { + AWSProviderBadge, + AzureProviderBadge, + GCPProviderBadge, + KS8ProviderBadge, +} from "@/components/icons/providers-badge"; +import { CustomButton } from "@/components/ui/custom/custom-button"; +import { ProviderOverviewProps } from "@/types"; + +export const ProvidersOverview = ({ + providersOverview, +}: { + providersOverview: ProviderOverviewProps; +}) => { + console.log(providersOverview); + if (!providersOverview || !Array.isArray(providersOverview.data)) { + return

No provider data available

; + } + + const calculatePassingPercentage = (pass: number, total: number) => + total > 0 ? ((pass / total) * 100).toFixed(2) : "0.00"; + + const renderProviderBadge = (providerId: string) => { + switch (providerId) { + case "aws": + return ; + case "azure": + return ; + case "gcp": + return ; + case "kubernetes": + return ; + default: + return null; + } + }; + + return ( + + +

Providers Overview

+
+ +
+
+ Provider + + Percent + Passing + + + Failing + Checks + + + Total + Resources + +
+ + {providersOverview.data.length === 0 ? ( +
+ - + - + - + - +
+ ) : ( + providersOverview.data.map((provider) => { + const { pass, fail, total } = provider.attributes.findings; + const resourcesTotal = provider.attributes.resources.total; + + return ( +
+ + {renderProviderBadge(provider.id)} + + + {calculatePassingPercentage(pass, total)}% + + {fail} + {resourcesTotal} +
+ ); + }) + )} +
+
+ } + > + Go to Providers + +
+
+
+ ); +}; diff --git a/components/overview/provider-overview/skeleton-provider-overview.tsx b/components/overview/provider-overview/skeleton-provider-overview.tsx new file mode 100644 index 0000000000..47febd1700 --- /dev/null +++ b/components/overview/provider-overview/skeleton-provider-overview.tsx @@ -0,0 +1,64 @@ +import { Card, CardBody, CardHeader, Skeleton } from "@nextui-org/react"; + +export const SkeletonProvidersOverview = () => { + const rows = 4; + + return ( + + + +
+
+
+ +
+ {/* Header Skeleton */} +
+ +
+
+ +
+
+ +
+
+ +
+
+
+ + {/* Row Skeletons */} + {Array.from({ length: rows }).map((_, index) => ( +
+ {/* Provider Name */} +
+ +
+
+ +
+
+
+ {/* Percent Passing */} + +
+
+ {/* Failing Checks */} + +
+
+ {/* Total Resources */} + +
+
+
+ ))} +
+
+
+ ); +}; diff --git a/types/components.ts b/types/components.ts index 7ec383d408..bbc7024646 100644 --- a/types/components.ts +++ b/types/components.ts @@ -26,6 +26,27 @@ export type NextUIColors = | "danger" | "default"; +export interface ProviderOverviewProps { + data: { + type: "provider-overviews"; + id: "aws" | "gcp" | "azure" | "kubernetes"; + attributes: { + findings: { + pass: number; + fail: number; + manual: number; + total: number; + }; + resources: { + total: number; + }; + }; + }[]; + meta: { + version: string; + }; +} + export interface TaskDetails { attributes: { state: string; From cfd4339c411652a2bf294d6815b06220600e36ec Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Mon, 25 Nov 2024 12:11:27 +0100 Subject: [PATCH 410/411] feat: render all providers with or without data --- .../provider-overview/provider-overview.tsx | 99 +++++++++++++------ 1 file changed, 69 insertions(+), 30 deletions(-) diff --git a/components/overview/provider-overview/provider-overview.tsx b/components/overview/provider-overview/provider-overview.tsx index 6c5edfb102..cd69f02595 100644 --- a/components/overview/provider-overview/provider-overview.tsx +++ b/components/overview/provider-overview/provider-overview.tsx @@ -40,6 +40,13 @@ export const ProvidersOverview = ({ } }; + const providers = [ + { id: "aws", name: "AWS" }, + { id: "azure", name: "Azure" }, + { id: "gcp", name: "GCP" }, + { id: "kubernetes", name: "Kubernetes" }, + ]; + return ( @@ -63,46 +70,78 @@ export const ProvidersOverview = ({
- {providersOverview.data.length === 0 ? ( -
- - - - - - - - -
- ) : ( - providersOverview.data.map((provider) => { - const { pass, fail, total } = provider.attributes.findings; - const resourcesTotal = provider.attributes.resources.total; + {providers.map((providerTemplate) => { + const providerData = providersOverview.data.find( + (p) => p.id === providerTemplate.id, + ); - return ( -
- - {renderProviderBadge(provider.id)} - - - {calculatePassingPercentage(pass, total)}% - - {fail} - {resourcesTotal} -
- ); - }) - )} + return ( +
+ + {renderProviderBadge(providerTemplate.id)} + + + {providerData + ? calculatePassingPercentage( + providerData.attributes.findings.pass, + providerData.attributes.findings.total, + ) + : "0.00"} + % + + + {providerData ? providerData.attributes.findings.fail : "-"} + + + {providerData ? providerData.attributes.resources.total : "-"} + +
+ ); + })} + + {/* Totals row */} +
+ Total + + {calculatePassingPercentage( + providersOverview.data.reduce( + (sum, provider) => sum + provider.attributes.findings.pass, + 0, + ), + providersOverview.data.reduce( + (sum, provider) => sum + provider.attributes.findings.total, + 0, + ), + )} + % + + + {providersOverview.data.reduce( + (sum, provider) => sum + provider.attributes.findings.fail, + 0, + )} + + + {providersOverview.data.reduce( + (sum, provider) => sum + provider.attributes.resources.total, + 0, + )} + +
} > - Go to Providers + Add Provider
From befcdd3dfae8b4d504188122bc9f9ded2622a259 Mon Sep 17 00:00:00 2001 From: Pedro De Castro <1519428+snaow@users.noreply.github.com> Date: Mon, 25 Nov 2024 13:06:04 +0100 Subject: [PATCH 411/411] Update and remove MD files preparing repositories merge (#99) --- LICENSE | 21 --------------------- README.md | 33 ++++++++++----------------------- 2 files changed, 10 insertions(+), 44 deletions(-) delete mode 100644 LICENSE diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 7f91f8483f..0000000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2023 Next UI - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/README.md b/README.md index c1da609412..3e334368ff 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,7 @@ -# Next.js & NextUI Template +# Description -This is a template for creating applications using Next.js 14 (app directory) and NextUI (v2). +This repository hosts the UI component for Prowler, providing a user-friendly web interface to interact seamlessly with Prowler's features. -[Try it on CodeSandbox](https://githubbox.com/nextui-org/next-app-template) - -## Technologies Used - -- [Next.js 14](https://nextjs.org/docs/getting-started) -- [NextUI v2](https://nextui.org/) -- [Tailwind CSS](https://tailwindcss.com/) -- [Tailwind Variants](https://tailwind-variants.org) -- [TypeScript](https://www.typescriptlang.org/) -- [Framer Motion](https://www.framer.com/motion/) -- [next-themes](https://github.com/pacocoursey/next-themes) - -## Use the template with create-next-app - -To create a new project based on this template using `create-next-app`, run the following command: - -```bash -npx create-next-app -e https://github.com/nextui-org/next-app-template -``` ## 🚀 Production deployment ### Docker deployment @@ -122,6 +103,12 @@ public-hoist-pattern[]=*@nextui-org/* After modifying the `.npmrc` file, you need to run `pnpm install` again to ensure that the dependencies are installed correctly. -## License +## Technologies Used -Licensed under the [MIT license](https://github.com/nextui-org/next-app-template/blob/main/LICENSE). +- [Next.js 14](https://nextjs.org/docs/getting-started) +- [NextUI v2](https://nextui.org/) +- [Tailwind CSS](https://tailwindcss.com/) +- [Tailwind Variants](https://tailwind-variants.org) +- [TypeScript](https://www.typescriptlang.org/) +- [Framer Motion](https://www.framer.com/motion/) +- [next-themes](https://github.com/pacocoursey/next-themes)