From ae12a067df4cf0076b70d6dd6ab8158e991a06bc Mon Sep 17 00:00:00 2001 From: Sidney Alcantara Date: Wed, 15 Sep 2021 22:07:40 +1000 Subject: [PATCH] Setup: add step 2 --- src/components/Setup/Step0Welcome.tsx | 71 ++++++--- src/components/Setup/Step1RowyRun.tsx | 12 +- src/components/Setup/Step2ServiceAccount.tsx | 157 +++++++++++++++++++ src/pages/Setup.tsx | 103 +++++------- 4 files changed, 252 insertions(+), 91 deletions(-) create mode 100644 src/components/Setup/Step2ServiceAccount.tsx diff --git a/src/components/Setup/Step0Welcome.tsx b/src/components/Setup/Step0Welcome.tsx index 083e4fdd..e4be86a0 100644 --- a/src/components/Setup/Step0Welcome.tsx +++ b/src/components/Setup/Step0Welcome.tsx @@ -3,36 +3,55 @@ import { ISetupStepBodyProps } from "pages/Setup"; import { FormControlLabel, Checkbox, Typography, Link } from "@mui/material"; import OpenInNewIcon from "components/InlineOpenInNewIcon"; +import { useAppContext } from "contexts/AppContext"; + export default function Welcome({ completion, setCompletion, }: ISetupStepBodyProps) { + const { projectId } = useAppContext(); + return ( - - setCompletion((c) => ({ ...c, welcome: e.target.checked })) - } - /> - } - label={ - <> - - I agree to the terms and conditions - - - Read the simple English version - - - - Read the full terms and conditions - - - - } - sx={{ pr: 1, textAlign: "left", alignItems: "flex-start", p: 0, m: 0 }} - /> + <> +
+ + Get up and running in around 5 minutes. + + + You’ll easily set up backend functionality, Firestore Rules, and user + management. + + + You’ll set up the project: {projectId} + +
+ + + setCompletion((c) => ({ ...c, welcome: e.target.checked })) + } + /> + } + label={ + <> + + I agree to the terms and conditions + + + Read the simple English version + + + + Read the full terms and conditions + + + + } + sx={{ pr: 1, textAlign: "left", alignItems: "flex-start", p: 0, m: 0 }} + /> + ); } diff --git a/src/components/Setup/Step1RowyRun.tsx b/src/components/Setup/Step1RowyRun.tsx index eed23ccf..0cae4567 100644 --- a/src/components/Setup/Step1RowyRun.tsx +++ b/src/components/Setup/Step1RowyRun.tsx @@ -9,6 +9,7 @@ import OpenInNewIcon from "@mui/icons-material/OpenInNew"; import SetupItem from "./SetupItem"; +import { name } from "@root/package.json"; import { runRepoUrl, RunRoutes } from "constants/runRoutes"; export default function Step1RowyRun({ @@ -34,7 +35,7 @@ export default function Step1RowyRun({ setVerificationStatus("loading"); try { - const result = await checkCompletionRowyRun(rowyRunUrl); + const result = await checkRowyRun(rowyRunUrl); setVerificationStatus("pass"); if (result.isValidRowyRunUrl) setIsValidRowyRunUrl(true); @@ -81,6 +82,12 @@ export default function Step1RowyRun({ return ( <> + + {name} Run is a Google Cloud Run instance that provides back-end + functionality, such as table action scripts, user management, and easy + Cloud Function deployment. + + )} - {isValidRowyRunUrl && ( { +export const checkRowyRun = async (rowyRunUrl: string) => { const result = { isValidRowyRunUrl: false, isLatestVersion: false, diff --git a/src/components/Setup/Step2ServiceAccount.tsx b/src/components/Setup/Step2ServiceAccount.tsx new file mode 100644 index 00000000..b1709cb3 --- /dev/null +++ b/src/components/Setup/Step2ServiceAccount.tsx @@ -0,0 +1,157 @@ +import { useState, useEffect } from "react"; +import { ISetupStepBodyProps } from "pages/Setup"; + +import { Typography, Link, Stack } from "@mui/material"; +import LoadingButton from "@mui/lab/LoadingButton"; +import OpenInNewIcon from "@mui/icons-material/OpenInNew"; +import InlineOpenInNewIcon from "components/InlineOpenInNewIcon"; +import InfoIcon from "@mui/icons-material/InfoOutlined"; + +import SetupItem from "./SetupItem"; + +import { name } from "@root/package.json"; +import { useAppContext } from "contexts/AppContext"; +import { RunRoutes } from "constants/runRoutes"; + +export default function Step2ServiceAccount({ + rowyRunUrl, + completion, + setCompletion, +}: ISetupStepBodyProps) { + const [hasAllRoles, setHasAllRoles] = useState(completion.serviceAccount); + const [verificationStatus, setVerificationStatus] = useState< + "idle" | "loading" | "pass" | "fail" + >("idle"); + + const { projectId } = useAppContext(); + const [region, setRegion] = useState(""); + useEffect(() => { + fetch(rowyRunUrl + RunRoutes.region.path, { + method: RunRoutes.region.method, + }) + .then((res) => res.json()) + .then((data) => setRegion(data.region)) + .catch((e) => console.error(e)); + }, []); + + const verifyRoles = async () => { + setVerificationStatus("loading"); + try { + const result = await checkServiceAccount(rowyRunUrl); + if (result) { + setVerificationStatus("pass"); + setHasAllRoles(true); + setCompletion((c) => ({ ...c, serviceAccount: true })); + } else { + setVerificationStatus("fail"); + setHasAllRoles(false); + } + } catch (e) { + console.error(e); + setVerificationStatus("fail"); + } + }; + + return ( + <> + + {name} Run uses the{" "} + + Firebase Admin SDK + {" "} + and{" "} + + Google Cloud SDKs + {" "} + to make changes to your Firestore database, authenticated with a{" "} + + service account + + . Rowy Run operates exclusively on your GCP project and we will never + have access to your service account or any of your data. + + + +
    +
  • Service Account User – required to deploy Cloud Functions
  • +
  • Firebase Authentication Admin
  • +
  • Firestore Service Agent
  • +
+ + {!hasAllRoles && ( + <> + + } + > + Set Up Service Account + + + Verify + + + + + + + On the Google Cloud Console page, click the Security tab to set + the service account for Rowy Run. + + + + )} + + + Learn about IAM roles + + +
+ + ); +} + +export const checkServiceAccount = async (rowyRunUrl: string) => { + const req = await fetch(rowyRunUrl + RunRoutes.serviceAccountAccess.path, { + method: RunRoutes.serviceAccountAccess.method, + }); + if (!req.ok) return false; + + const res = await req.json(); + return Object.values(res).reduce( + (acc, value) => acc && value, + true + ) as boolean; +}; diff --git a/src/pages/Setup.tsx b/src/pages/Setup.tsx index 1004b035..823bd1fd 100644 --- a/src/pages/Setup.tsx +++ b/src/pages/Setup.tsx @@ -27,21 +27,20 @@ import Logo from "assets/Logo"; import ScrollableDialogContent from "components/Modal/ScrollableDialogContent"; import { SlideTransition } from "components/Modal/SlideTransition"; -import Step0Welcome from "@src/components/Setup/Step0Welcome"; -import Step1RowyRun, { - checkCompletionRowyRun, -} from "@src/components/Setup/Step1RowyRun"; +import Step0Welcome from "components/Setup/Step0Welcome"; +import Step1RowyRun, { checkRowyRun } from "components/Setup/Step1RowyRun"; +// prettier-ignore +import Step2ServiceAccount, { checkServiceAccount } from "components/Setup/Step2ServiceAccount"; -import { useAppContext } from "contexts/AppContext"; import { name } from "@root/package.json"; import routes from "constants/routes"; export interface ISetupStep { id: string; - layout?: "centered" | "step"; + layout?: "centered"; shortTitle: string; title: React.ReactNode; - description: React.ReactNode; + description?: React.ReactNode; body: React.ReactNode; actions?: React.ReactNode; } @@ -60,9 +59,12 @@ const checkAllSteps = async ( console.log("Check all steps"); const completion: Record = {}; - const checkRowyRun = await checkCompletionRowyRun(rowyRunUrl); - if (checkRowyRun.isValidRowyRunUrl) { - if (checkRowyRun.isLatestVersion) completion.rowyRun = true; + const rowyRunValidation = await checkRowyRun(rowyRunUrl); + if (rowyRunValidation.isValidRowyRunUrl) { + if (rowyRunValidation.isLatestVersion) completion.rowyRun = true; + + const serviceAccount = await checkServiceAccount(rowyRunUrl); + if (serviceAccount) completion.serviceAccount = true; } if (Object.keys(completion).length > 0) @@ -70,7 +72,6 @@ const checkAllSteps = async ( }; export default function SetupPage() { - const { projectId } = useAppContext(); const fullScreenHeight = use100vh() ?? 0; const isMobile = useMediaQuery((theme: any) => theme.breakpoints.down("sm")); @@ -85,7 +86,6 @@ export default function SetupPage() { serviceAccount: false, signIn: false, rules: false, - migrate: false, }); useEffect(() => { @@ -97,23 +97,9 @@ export default function SetupPage() { const steps: ISetupStep[] = [ { id: "welcome", - layout: "centered", + layout: "centered" as "centered", shortTitle: "Welcome", title: `Welcome to ${name}`, - description: ( - <> - - Get up and running in around 5 minutes. - - - You’ll easily set up backend functionality, Firestore Rules, and - user management. - - - You’ll set up the project: {projectId} - - - ), body: , actions: completion.welcome ? (