From 54dad9b1875e85f0d799e765f6806b31b18ea9cf Mon Sep 17 00:00:00 2001 From: Karol Wypchlo Date: Wed, 3 Feb 2021 15:06:44 +0100 Subject: [PATCH] custom frontend --- docker-compose.accounts.yml | 22 + docker/kratos/oathkeeper/access-rules.yml | 10 +- packages/dashboard/.gitignore | 35 ++ packages/dashboard/.prettierignore | 3 + packages/dashboard/.prettierrc | 3 + packages/dashboard/Dockerfile | 15 + packages/dashboard/README.md | 34 ++ packages/dashboard/package.json | 21 + packages/dashboard/pages/_app.js | 7 + packages/dashboard/pages/api/hello.js | 6 + packages/dashboard/pages/auth/login.js | 148 +++++++ packages/dashboard/pages/auth/recovery.js | 120 ++++++ packages/dashboard/pages/auth/registration.js | 140 ++++++ packages/dashboard/pages/downloads.js | 28 ++ packages/dashboard/pages/index.js | 5 + packages/dashboard/pages/payments.js | 401 ++++++++++++++++++ packages/dashboard/pages/uploads.js | 28 ++ packages/dashboard/postcss.config.js | 6 + packages/dashboard/public/favicon.ico | Bin 0 -> 2118 bytes packages/dashboard/src/components/Layout.js | 225 ++++++++++ packages/dashboard/src/components/Table.js | 89 ++++ packages/dashboard/src/config.js | 39 ++ packages/dashboard/styles/Home.module.css | 122 ++++++ packages/dashboard/styles/globals.css | 16 + packages/dashboard/tailwind.config.js | 11 + 25 files changed, 1529 insertions(+), 5 deletions(-) create mode 100644 packages/dashboard/.gitignore create mode 100644 packages/dashboard/.prettierignore create mode 100644 packages/dashboard/.prettierrc create mode 100644 packages/dashboard/Dockerfile create mode 100644 packages/dashboard/README.md create mode 100644 packages/dashboard/package.json create mode 100644 packages/dashboard/pages/_app.js create mode 100644 packages/dashboard/pages/api/hello.js create mode 100644 packages/dashboard/pages/auth/login.js create mode 100644 packages/dashboard/pages/auth/recovery.js create mode 100644 packages/dashboard/pages/auth/registration.js create mode 100644 packages/dashboard/pages/downloads.js create mode 100644 packages/dashboard/pages/index.js create mode 100644 packages/dashboard/pages/payments.js create mode 100644 packages/dashboard/pages/uploads.js create mode 100644 packages/dashboard/postcss.config.js create mode 100644 packages/dashboard/public/favicon.ico create mode 100644 packages/dashboard/src/components/Layout.js create mode 100644 packages/dashboard/src/components/Table.js create mode 100644 packages/dashboard/src/config.js create mode 100644 packages/dashboard/styles/Home.module.css create mode 100644 packages/dashboard/styles/globals.css create mode 100644 packages/dashboard/tailwind.config.js diff --git a/docker-compose.accounts.yml b/docker-compose.accounts.yml index 02d29989..1bd1f771 100644 --- a/docker-compose.accounts.yml +++ b/docker-compose.accounts.yml @@ -128,6 +128,28 @@ services: shared: ipv4_address: 10.10.10.82 + dashboard: + build: + context: ./packages/dashboard + dockerfile: Dockerfile + container_name: dashboard + restart: unless-stopped + logging: *default-logging + environment: + - SECURITY_MODE=jwks + - PROJECT_NAME=Skynet + - BASE_URL=/ + - KRATOS_BROWSER_URL=/.ory/kratos/public + - JWKS_URL=http://oathkeeper:4456/.well-known/jwks.json + - KRATOS_PUBLIC_URL=http://kratos:4433/ + - KRATOS_ADMIN_URL=http://kratos:4434/ + - SQA_OPT_OUT=true + networks: + shared: + ipv4_address: 10.10.10.83 + expose: + - 3000 + oathkeeper: image: oryd/oathkeeper:v0.38 container_name: oathkeeper diff --git a/docker/kratos/oathkeeper/access-rules.yml b/docker/kratos/oathkeeper/access-rules.yml index 5e0e03fe..e6f4b296 100644 --- a/docker/kratos/oathkeeper/access-rules.yml +++ b/docker/kratos/oathkeeper/access-rules.yml @@ -18,10 +18,10 @@ mutators: - handler: noop -- id: "ory:kratos-selfservice-ui-node:anonymous" +- id: "dashboard:anonymous" upstream: preserve_host: true - url: "http://kratos-selfservice-ui-node:4435" + url: "http://dashboard:3000" match: url: "http://oathkeeper:4455/<{error,recovery,verify,auth/*,**.css,**.js}{/,}>" methods: @@ -33,12 +33,12 @@ mutators: - handler: noop -- id: "ory:kratos-selfservice-ui-node:protected" +- id: "dashboard:protected" upstream: preserve_host: true - url: "http://kratos-selfservice-ui-node:4435" + url: "http://dashboard:3000" match: - url: "http://oathkeeper:4455/<{,debug,dashboard,settings}>" + url: "http://oathkeeper:4455/<{,uploads,downloads,payments}>" methods: - GET authenticators: diff --git a/packages/dashboard/.gitignore b/packages/dashboard/.gitignore new file mode 100644 index 00000000..173ca174 --- /dev/null +++ b/packages/dashboard/.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 +.env.development.local +.env.test.local +.env.production.local + +# vercel +.vercel +.next diff --git a/packages/dashboard/.prettierignore b/packages/dashboard/.prettierignore new file mode 100644 index 00000000..3103ecb0 --- /dev/null +++ b/packages/dashboard/.prettierignore @@ -0,0 +1,3 @@ +.next +package.json +package-lock.json \ No newline at end of file diff --git a/packages/dashboard/.prettierrc b/packages/dashboard/.prettierrc new file mode 100644 index 00000000..963354f2 --- /dev/null +++ b/packages/dashboard/.prettierrc @@ -0,0 +1,3 @@ +{ + "printWidth": 120 +} diff --git a/packages/dashboard/Dockerfile b/packages/dashboard/Dockerfile new file mode 100644 index 00000000..18d5c3b5 --- /dev/null +++ b/packages/dashboard/Dockerfile @@ -0,0 +1,15 @@ +FROM node:14.15.0-alpine + +WORKDIR /usr/app + +COPY pages ./pages +COPY public ./public +COPY src ./src +COPY styles ./styles +COPY package.json . +COPY postcss.config.js . +COPY tailwind.config.js . + +ENV NEXT_TELEMETRY_DISABLED 1 +RUN yarn --no-lockfile && yarn build +CMD ["yarn", "start"] diff --git a/packages/dashboard/README.md b/packages/dashboard/README.md new file mode 100644 index 00000000..4b412a3c --- /dev/null +++ b/packages/dashboard/README.md @@ -0,0 +1,34 @@ +This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). + +## Getting Started + +First, run the development server: + +```bash +npm run dev +# or +yarn dev +``` + +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. + +You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file. + +[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.js`. + +The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages. + +## Learn More + +To learn more about Next.js, take a look at the following resources: + +- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. +- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. + +You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! + +## Deploy on Vercel + +The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/import?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. + +Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. diff --git a/packages/dashboard/package.json b/packages/dashboard/package.json new file mode 100644 index 00000000..1cbc5717 --- /dev/null +++ b/packages/dashboard/package.json @@ -0,0 +1,21 @@ +{ + "name": "dashboard", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start" + }, + "dependencies": { + "@ory/kratos-client": "^0.5.4-alpha.1", + "autoprefixer": "^10.2.4", + "next": "^10.0.6", + "postcss": "^8.2.4", + "prettier": "^2.2.1", + "react": "17.0.1", + "react-dom": "17.0.1", + "swr": "^0.4.1", + "tailwindcss": "^2.0.2" + } +} diff --git a/packages/dashboard/pages/_app.js b/packages/dashboard/pages/_app.js new file mode 100644 index 00000000..9917eca6 --- /dev/null +++ b/packages/dashboard/pages/_app.js @@ -0,0 +1,7 @@ +import "tailwindcss/tailwind.css"; + +function MyApp({ Component, pageProps }) { + return ; +} + +export default MyApp; diff --git a/packages/dashboard/pages/api/hello.js b/packages/dashboard/pages/api/hello.js new file mode 100644 index 00000000..06c71f46 --- /dev/null +++ b/packages/dashboard/pages/api/hello.js @@ -0,0 +1,6 @@ +// Next.js API route support: https://nextjs.org/docs/api-routes/introduction + +export default (req, res) => { + res.statusCode = 200; + res.json({ name: "John Doe" }); +}; diff --git a/packages/dashboard/pages/auth/login.js b/packages/dashboard/pages/auth/login.js new file mode 100644 index 00000000..5f981d94 --- /dev/null +++ b/packages/dashboard/pages/auth/login.js @@ -0,0 +1,148 @@ +import Link from "next/link"; +import { Configuration, PublicApi } from "@ory/kratos-client"; +import config from "../../src/config"; + +const kratos = new PublicApi(new Configuration({ basePath: config.kratos.public })); + +export async function getServerSideProps(context) { + const flow = context.query.flow; + + // The flow is used to identify the login and registration flow and + // return data like the csrf_token and so on. + if (!flow || typeof flow !== "string") { + console.log("No flow ID found in URL, initializing login flow."); + + return { + redirect: { + permanent: false, + destination: `${config.kratos.browser}/self-service/login/browser`, + }, + }; + } + + try { + const { status, data } = await kratos.getSelfServiceLoginFlow(flow); + + if (status === 200) return { props: { flow: data } }; + + throw new Error(`Failed to retrieve flow ${flow} with code ${status}`); + } catch (error) { + return { + redirect: { + permanent: false, + destination: `${config.kratos.browser}/self-service/login/browser`, + }, + }; + } +} + +const fieldProps = { + identifier: { + label: "Email address", + autoComplete: "email", + position: 0, + }, + password: { + label: "Password", + autoComplete: "current-password", + position: 1, + }, + csrf_token: { + position: 99, + }, +}; + +export default function Login({ flow }) { + const fields = flow.methods.password.config.fields + .map((field) => ({ + ...field, + ...fieldProps[field.name], + })) + .sort((a, b) => (a.position < b.position ? -1 : 1)); + + console.log(flow); + + return ( +
+
+ + + +

Sign in to your account

+

+ or{" "} + + sign up + {" "} + if you don't have one yet +

+
+
+
+
+ {fields.map((field) => ( +
+ {field.type !== "hidden" && ( + + )} +
+ +
+
+ ))} +
+ {/*
+ + +
*/} + +
+
+ +
+
+
+
+
+ ); +} diff --git a/packages/dashboard/pages/auth/recovery.js b/packages/dashboard/pages/auth/recovery.js new file mode 100644 index 00000000..0076bd86 --- /dev/null +++ b/packages/dashboard/pages/auth/recovery.js @@ -0,0 +1,120 @@ +import Link from "next/link"; +import { Configuration, PublicApi } from "@ory/kratos-client"; +import config from "../../src/config"; + +const kratos = new PublicApi(new Configuration({ basePath: config.kratos.public })); + +export async function getServerSideProps(context) { + const flow = context.query.flow; + + // The flow is used to identify the login and registration flow and + // return data like the csrf_token and so on. + if (!flow || typeof flow !== "string") { + console.log("No flow ID found in URL, initializing recovery flow."); + + return { + redirect: { + permanent: false, + destination: `${config.kratos.browser}/self-service/recovery/browser`, + }, + }; + } + + try { + const { status, data } = await kratos.getSelfServiceRecoveryFlow(flow); + + if (status === 200) return { props: { flow: data } }; + + throw new Error(`Failed to retrieve flow ${flow} with code ${status}`); + } catch (error) { + return { + redirect: { + permanent: false, + destination: `${config.kratos.browser}/self-service/recovery/browser`, + }, + }; + } +} + +const fieldProps = { + email: { + label: "Your email", + autoComplete: "email", + position: 0, + }, + csrf_token: { + position: 99, + }, +}; + +export default function Registration({ flow }) { + const fields = flow.methods.link.config.fields + .map((field) => ({ + ...field, + ...fieldProps[field.name], + })) + .sort((a, b) => (a.position < b.position ? -1 : 1)); + + console.log(flow); + return ( +
+
+ + + +

Recover your account

+

+ or{" "} + + sign in + {" "} + if you suddenly remembered your password +

+
+
+
+
+ {fields.map((field) => ( +
+ {field.type !== "hidden" && ( + + )} +
+ +
+
+ ))} +
+ +
+
+
+
+
+ ); +} diff --git a/packages/dashboard/pages/auth/registration.js b/packages/dashboard/pages/auth/registration.js new file mode 100644 index 00000000..8762dbaa --- /dev/null +++ b/packages/dashboard/pages/auth/registration.js @@ -0,0 +1,140 @@ +import Link from "next/link"; +import { Configuration, PublicApi } from "@ory/kratos-client"; +import config from "../../src/config"; + +const kratos = new PublicApi(new Configuration({ basePath: config.kratos.public })); + +export async function getServerSideProps(context) { + const flow = context.query.flow; + + // The flow is used to identify the login and registration flow and + // return data like the csrf_token and so on. + if (!flow || typeof flow !== "string") { + console.log("No flow ID found in URL, initializing registration flow."); + + return { + redirect: { + permanent: false, + destination: `${config.kratos.browser}/self-service/registration/browser`, + }, + }; + } + + try { + const { status, data } = await kratos.getSelfServiceRegistrationFlow(flow); + + if (status === 200) return { props: { flow: data } }; + + throw new Error(`Failed to retrieve flow ${flow} with code ${status}`); + } catch (error) { + return { + redirect: { + permanent: false, + destination: `${config.kratos.browser}/self-service/registration/browser`, + }, + }; + } +} + +const fieldProps = { + "traits.email": { + label: "Email address", + autoComplete: "email", + position: 0, + }, + "traits.name.first": { + label: "First name", + autoComplete: "given-name", + position: 1, + }, + "traits.name.last": { + label: "Last name", + autoComplete: "family-name", + position: 2, + }, + password: { + label: "Password", + autoComplete: "new-password", + position: 4, + }, + csrf_token: { + position: 99, + }, +}; + +export default function Registration({ flow }) { + const fields = flow.methods.password.config.fields + .map((field) => ({ + ...field, + ...fieldProps[field.name], + })) + .sort((a, b) => (a.position < b.position ? -1 : 1)); + + console.log(flow); + + return ( +
+
+ + + +

Sign up for a new account

+

+ or{" "} + + sign in + {" "} + if you already have one +

+
+
+
+
+ {fields.map((field) => ( +
+ {field.type !== "hidden" && ( + + )} +
+ +
+
+ ))} +
+ +
+
+
+
+
+ ); +} diff --git a/packages/dashboard/pages/downloads.js b/packages/dashboard/pages/downloads.js new file mode 100644 index 00000000..158efec7 --- /dev/null +++ b/packages/dashboard/pages/downloads.js @@ -0,0 +1,28 @@ +import { useState } from "react"; +import Layout from "../src/components/Layout"; +import Table from "../src/components/Table"; + +const data = [ + { skylink: "PAL0w4SdA5rFCDGEutgpeQ50Om-YkBabtXVOJAkmedslKw", size: "1 KB", date: "today" }, + { skylink: "XABvi7JtJbQSMAcDwnUnmp2FKDPjg8_tTTFP4BwMSxVdEg", size: "102 MB", date: "today" }, + { skylink: "IADUs8d9CQjUO34LmdaaNPK_STuZo24rpKVfYW3wPPM2uQ", size: "141 KB", date: "today" }, + { skylink: "_A2zt5SKoqwnnZU4cBF8uBycSKULXMyeg1c5ZISBr2Q3dA", size: "1 KB", date: "today" }, + { skylink: "AAC0uO43g64ULpyrW0zO3bjEknSFbAhm8c-RFP21EQlmSQ", size: "0 KB", date: "today" }, + { skylink: "CACqf4NlIMlA0CCCieYGjpViPGyfyJ4v1x3bmuCKZX8FKA", size: "1.3 MB", date: "today" }, +]; +const headers = [ + { key: "skylink", name: "Skylink" }, + { key: "size", name: "Size" }, + { key: "date", name: "Access time" }, +]; +const actions = []; + +export default function Downloads() { + const [page, setPage] = useState(1); + + return ( + + + + ); +} diff --git a/packages/dashboard/pages/index.js b/packages/dashboard/pages/index.js new file mode 100644 index 00000000..e696a8ff --- /dev/null +++ b/packages/dashboard/pages/index.js @@ -0,0 +1,5 @@ +import Layout from "../src/components/Layout"; + +export default function Home() { + return ; +} diff --git a/packages/dashboard/pages/payments.js b/packages/dashboard/pages/payments.js new file mode 100644 index 00000000..a5ff14fa --- /dev/null +++ b/packages/dashboard/pages/payments.js @@ -0,0 +1,401 @@ +import Layout from "../src/components/Layout"; + +export default function Payments() { + return ( + +
+ {/* This example requires Tailwind CSS v2.0+ */} +
+
+
+
Current plan
+
Free Plan
+
+
+
+
+
Next invoice
+
-
+
+
+
+
+
Plan usage this month
+
24.57%
+
+
+
+ + {/* This example requires Tailwind CSS v2.0+ */} +
+
+
+

Pricing Plans

+

+ Start building for free, then add a site plan to go live. Account plans unlock additional features. +

+
+
+
+
+

Hobby

+

All the basics for starting a new business

+

+ $12 + /mo +

+ + Buy Hobby + +
+
+

What's included

+
    +
  • + {/* Heroicon name: check */} + + Potenti felis, in cras at at ligula nunc. +
  • +
  • + {/* Heroicon name: check */} + + Orci neque eget pellentesque. +
  • +
+
+
+
+
+

Freelancer

+

All the basics for starting a new business

+

+ $24 + /mo +

+ + Buy Freelancer + +
+
+

What's included

+
    +
  • + {/* Heroicon name: check */} + + Potenti felis, in cras at at ligula nunc. +
  • +
  • + {/* Heroicon name: check */} + + Orci neque eget pellentesque. +
  • +
  • + {/* Heroicon name: check */} + + Donec mauris sit in eu tincidunt etiam. +
  • +
+
+
+
+
+

Startup

+

All the basics for starting a new business

+

+ $32 + /mo +

+ + Buy Startup + +
+
+

What's included

+
    +
  • + {/* Heroicon name: check */} + + Potenti felis, in cras at at ligula nunc. +
  • +
  • + {/* Heroicon name: check */} + + Orci neque eget pellentesque. +
  • +
  • + {/* Heroicon name: check */} + + Donec mauris sit in eu tincidunt etiam. +
  • +
  • + {/* Heroicon name: check */} + + Faucibus volutpat magna. +
  • +
+
+
+
+
+

Enterprise

+

All the basics for starting a new business

+

+ $48 + /mo +

+ + Buy Enterprise + +
+
+

What's included

+
    +
  • + {/* Heroicon name: check */} + + Potenti felis, in cras at at ligula nunc. +
  • +
  • + {/* Heroicon name: check */} + + Orci neque eget pellentesque. +
  • +
  • + {/* Heroicon name: check */} + + Donec mauris sit in eu tincidunt etiam. +
  • +
  • + {/* Heroicon name: check */} + + Faucibus volutpat magna. +
  • +
  • + {/* Heroicon name: check */} + + Id sed tellus in varius quisque. +
  • +
  • + {/* Heroicon name: check */} + + Risus egestas faucibus. +
  • +
  • + {/* Heroicon name: check */} + + Risus cursus ullamcorper. +
  • +
+
+
+
+
+
+
+
+ ); +} diff --git a/packages/dashboard/pages/uploads.js b/packages/dashboard/pages/uploads.js new file mode 100644 index 00000000..543280fe --- /dev/null +++ b/packages/dashboard/pages/uploads.js @@ -0,0 +1,28 @@ +import { useState } from "react"; +import Layout from "../src/components/Layout"; +import Table from "../src/components/Table"; + +const data = [ + { skylink: "PAL0w4SdA5rFCDGEutgpeQ50Om-YkBabtXVOJAkmedslKw", size: "1 KB", date: "today" }, + { skylink: "XABvi7JtJbQSMAcDwnUnmp2FKDPjg8_tTTFP4BwMSxVdEg", size: "102 MB", date: "today" }, + { skylink: "IADUs8d9CQjUO34LmdaaNPK_STuZo24rpKVfYW3wPPM2uQ", size: "141 KB", date: "today" }, + { skylink: "_A2zt5SKoqwnnZU4cBF8uBycSKULXMyeg1c5ZISBr2Q3dA", size: "1 KB", date: "today" }, + { skylink: "AAC0uO43g64ULpyrW0zO3bjEknSFbAhm8c-RFP21EQlmSQ", size: "0 KB", date: "today" }, + { skylink: "CACqf4NlIMlA0CCCieYGjpViPGyfyJ4v1x3bmuCKZX8FKA", size: "1.3 MB", date: "today" }, +]; +const headers = [ + { key: "skylink", name: "Skylink" }, + { key: "size", name: "Size" }, + { key: "date", name: "Uploaded on" }, +]; +const actions = [{ key: "delete", name: "Delete", action: () => undefined }]; + +export default function Uploads() { + const [page, setPage] = useState(1); + + return ( + +
+ + ); +} diff --git a/packages/dashboard/postcss.config.js b/packages/dashboard/postcss.config.js new file mode 100644 index 00000000..12a703d9 --- /dev/null +++ b/packages/dashboard/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/packages/dashboard/public/favicon.ico b/packages/dashboard/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..9229fbf780c52d9d25e2183c0effe239dd2728dd GIT binary patch literal 2118 zcmV-M2)Xx(P)RT(~arVF-3X?a8o@+eOO1vEksNT^L4i?pS? zGjpFa_ntF5TNVgSR!EH+O{o}6G@z1bNRVez0u;Kt_nv!ax2u$A6CgkW0YZ2+L`XDR z1#DW{()IiQb7pobr6JWx{@i=coOAyFeE;`9t`LXuTbZsA!xQ~t-Jh2TFH3}{6DQEJ z6zHVn=d$E!>4ZfC^CXFAOg6;-{0rK#2(l(bi6EE4Cxy8zUvSIUSxJyd0Rx1evq8d3 zha9Xa(>gb=iy)OEOqCGRhoRgjg}D-58;WpffF}-SLzdsS%z?raVZb67@GTf{F2-MV z(;;3%66v9mpUF~Vb13?^4dbll7gOv$>{{hgcsXEBn}Asfe+!Jr!P=_3OLR`PC=u47m}Isz}7LsTJhYKlCX6 zBrpm{_G9fwFm8vh)jxegBJUdaX<*jugv4$kh1*sRUB<(^s6k0J8xl1@Q`Ifu62!Wz z+yV4rnwc|67!;s5J#&f^`FEE?D^M;J+P z;%n0lV?YH4c$so)2pvDO1^^yEi{W=B-2JRDm%Hq(_dtH+>c$*)kzLPGfJosn$QKta zdXmew2L_S72C-xyFa#|NKOLOcpAXIQu&HjCTdHZ^SoVznTV2RCE8l@Qznmh#O=J3h z78I6XfMX0$4v)2*l}sob3GuObZu4h&@jgg+93dg9d26nxo0q_V3qw69jAg11h#;Qj zDR}?8_2qtS+zU2;*M?vpB_R1gT_ocp*w^H!af_x%#v;V~H0>t?U?2=j)mQNTdY7PD zZXhCJQB%m#rURBP$K+3N@Hbh3=G!-{-;CwC{F!BN-%?!>mGOpHxy46*@Pyi|a1O0SL$oa(Oxy+yS{A3FTxaVkXbQxJ`r$#~X7z z9fPQnAa>SWcDz`7_lT%XxTM6vdWYOb$_E}95$<@4!#JM08fQ1lVbNqhSso#BHJ{WcsMGC+s#y>%;d%dC-$XAo6C?u463wTSt8Zevs@3$fuj%5eDahUm$bsR1c?k;$bnU{Q6G z%e5hDFGq%yXJFl#IBPZxFwkdI8<>z~c0H(G+CkpL$0@rrh6 z!b;0^Hrf0IDAoPZmRK~kif*_X;|+K=K;SP^z5r6+!1y}IjgWpAu)v^VWaa$>qR}3v zGas{V*m7Xe(PRkxXg2%`;~!Ar2(tJz&iRIrFF>whcV)?!F!w{qpRoSdE^(;fig+sT z)pDGaKyHFu>ZNk7Z#Xh)4S3=J0}u%!EL15d*q8U%n?De0HMc&=a~u3_7^$sr*=Z5=#{DWog{4r;P%E_h zCCAPZsfPA)*!UqQk7VpavYAR>lqbtMjYx2N<{jkw26<%#wZXJgG+ z*N|LpM^^j=o(nl5vj6G{7H?h8Y>jY>(6n6>)&N^XP*^gaKLT)S=|n$IDgsqZlJ*-%E4s_CX$-kA59MkIQa@fv`6?8@k zw^$U8So06Ne!n&3U&N(q*60gNWKkFOY>#U${)_>uw%0_Zh=dv&7HaewVf{yk?y^PX zX*3dZO~zAYCT&K^hMG}%GUjzB)AbYvw~dZ-5; z!<>{o530&fZU3Nf`+3q7K-7m>-d9&P+7gDpAywt9R6xgS?w{vDC|LpBIEbSh$nHyP0=?P zw;tMn89qqS+)N~k&F7e|6B6ir;v;d3??cJ&F#iP?H%Mm!b#eF8VSrZkti zGFk(^_;+E!A%^ce)A_=Ha+r79E$ViFRx$_Ca4go%YaylE-qQ4i87Y>Id~#4Me|QZS zy~FwqZM;@g@x?SS6;hRRY?lL)suBzkoldE#L;6}S+wRy2CSwue3)wHE_`m)L)*x*d wcr!_T1R3R2QHrnKyh^M|E8>b`?IF+j55uL_p0enzz5oCK07*qoM6N<$f=9md3IG5A literal 0 HcmV?d00001 diff --git a/packages/dashboard/src/components/Layout.js b/packages/dashboard/src/components/Layout.js new file mode 100644 index 00000000..39dafadf --- /dev/null +++ b/packages/dashboard/src/components/Layout.js @@ -0,0 +1,225 @@ +import Link from "next/link"; +import Head from "next/head"; +import { useState } from "react"; + +export default function Layout({ title, children }) { + const [menuOpen, openMenu] = useState(false); + const [avatarDropdownOpen, openAvatarDropdown] = useState(false); + + return ( +
+ + Skynet - {title} + + +
+ +
+
+

{title}

+
+
+
+
+
+ {/* Replace with your content */} + {children || ( +
+
+
+ )} + {/* /End replace */} +
+
+
+ ); +} diff --git a/packages/dashboard/src/components/Table.js b/packages/dashboard/src/components/Table.js new file mode 100644 index 00000000..962803cc --- /dev/null +++ b/packages/dashboard/src/components/Table.js @@ -0,0 +1,89 @@ +const rowsPerPage = 10; + +export default function Table({ data, headers, actions, page, setPage }) { + const lastPage = Math.ceil(data.length / rowsPerPage); + + if (page > lastPage) setPage(lastPage); + + const dataSlice = data.slice(rowsPerPage * (page - 1), rowsPerPage * (page - 1) + rowsPerPage); + + return ( +
+
+
+
+
+ + + {headers.map(({ key, name }) => ( + + ))} + {actions.map(({ key, name }) => ( + + ))} + + + + {dataSlice.map((row, index) => ( + + {headers.map(({ key, name }) => ( + + ))} + {actions.map(({ key, name, action }) => ( + + ))} + + ))} + +
+ {name} + + {name} +
+ {row[key] || "-"} + + + {name} + +
+ {/* This example requires Tailwind CSS v2.0+ */} + + + + + + ); +} diff --git a/packages/dashboard/src/config.js b/packages/dashboard/src/config.js new file mode 100644 index 00000000..8db99504 --- /dev/null +++ b/packages/dashboard/src/config.js @@ -0,0 +1,39 @@ +export const SECURITY_MODE_STANDALONE = "cookie"; +export const SECURITY_MODE_JWT = "jwt"; + +const baseUrl = process.env.BASE_URL || "/"; + +let securityMode = SECURITY_MODE_STANDALONE; +let browserUrl = process.env.KRATOS_BROWSER_URL || "https://secure.siasky.xyz/.ory/kratos/public"; +let publicUrl = process.env.KRATOS_PUBLIC_URL || "https://secure.siasky.xyz/.ory/kratos/public"; +switch ((process.env.SECURITY_MODE || "").toLowerCase()) { + case "jwt": + case "oathkeeper": + securityMode = SECURITY_MODE_JWT; + break; + case "cookie": + case "standalone": + default: + securityMode = SECURITY_MODE_STANDALONE; +} + +export default { + kratos: { + browser: browserUrl.replace(/\/+$/, ""), + admin: (process.env.KRATOS_ADMIN_URL || "").replace(/\/+$/, ""), + public: publicUrl.replace(/\/+$/, ""), + }, + baseUrl, + jwksUrl: process.env.JWKS_URL || "/", + projectName: process.env.PROJECT_NAME || "SecureApp", + + securityMode, + SECURITY_MODE_JWT, + SECURITY_MODE_STANDALONE, + + https: { + enabled: process.env.hasOwnProperty("TLS_KEY_PATH") && process.env.hasOwnProperty("TLS_CERT_PATH"), + certificatePath: process.env.TLS_CERT_PATH || "", + keyPath: process.env.TLS_KEY_PATH || "", + }, +}; diff --git a/packages/dashboard/styles/Home.module.css b/packages/dashboard/styles/Home.module.css new file mode 100644 index 00000000..80745811 --- /dev/null +++ b/packages/dashboard/styles/Home.module.css @@ -0,0 +1,122 @@ +.container { + min-height: 100vh; + padding: 0 0.5rem; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} + +.main { + padding: 5rem 0; + flex: 1; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} + +.footer { + width: 100%; + height: 100px; + border-top: 1px solid #eaeaea; + display: flex; + justify-content: center; + align-items: center; +} + +.footer img { + margin-left: 0.5rem; +} + +.footer a { + display: flex; + justify-content: center; + align-items: center; +} + +.title a { + color: #0070f3; + text-decoration: none; +} + +.title a:hover, +.title a:focus, +.title a:active { + text-decoration: underline; +} + +.title { + margin: 0; + line-height: 1.15; + font-size: 4rem; +} + +.title, +.description { + text-align: center; +} + +.description { + line-height: 1.5; + font-size: 1.5rem; +} + +.code { + background: #fafafa; + border-radius: 5px; + padding: 0.75rem; + font-size: 1.1rem; + font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, + monospace; +} + +.grid { + display: flex; + align-items: center; + justify-content: center; + flex-wrap: wrap; + max-width: 800px; + margin-top: 3rem; +} + +.card { + margin: 1rem; + flex-basis: 45%; + padding: 1.5rem; + text-align: left; + color: inherit; + text-decoration: none; + border: 1px solid #eaeaea; + border-radius: 10px; + transition: color 0.15s ease, border-color 0.15s ease; +} + +.card:hover, +.card:focus, +.card:active { + color: #0070f3; + border-color: #0070f3; +} + +.card h3 { + margin: 0 0 1rem 0; + font-size: 1.5rem; +} + +.card p { + margin: 0; + font-size: 1.25rem; + line-height: 1.5; +} + +.logo { + height: 1em; +} + +@media (max-width: 600px) { + .grid { + width: 100%; + flex-direction: column; + } +} diff --git a/packages/dashboard/styles/globals.css b/packages/dashboard/styles/globals.css new file mode 100644 index 00000000..e2e6d0f0 --- /dev/null +++ b/packages/dashboard/styles/globals.css @@ -0,0 +1,16 @@ +html, +body { + padding: 0; + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, + Helvetica Neue, sans-serif; +} + +a { + color: inherit; + text-decoration: none; +} + +* { + box-sizing: border-box; +} diff --git a/packages/dashboard/tailwind.config.js b/packages/dashboard/tailwind.config.js new file mode 100644 index 00000000..c1b1ca54 --- /dev/null +++ b/packages/dashboard/tailwind.config.js @@ -0,0 +1,11 @@ +module.exports = { + purge: ["./pages/**/*.js", "./components/**/*.js"], + darkMode: false, // or 'media' or 'class' + theme: { + extend: {}, + }, + variants: { + extend: {}, + }, + plugins: [], +};