From 5583714c7a805ce412d9e6d260ebb8ffc3c98ca0 Mon Sep 17 00:00:00 2001 From: Sophia Dao Date: Tue, 25 Jun 2024 13:58:54 -0500 Subject: [PATCH] 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());