add step 3

This commit is contained in:
Sidney Alcantara
2021-09-17 11:10:48 +10:00
parent 9a616924c5
commit 3c852259db
13 changed files with 413 additions and 105 deletions

View File

@@ -16,7 +16,7 @@ import InlineOpenInNewIcon from "components/InlineOpenInNewIcon";
import { IProjectSettingsChildProps } from "pages/Settings/ProjectSettings";
import WIKI_LINKS from "constants/wikiLinks";
import { name } from "@root/package.json";
import { RunRoutes, runRepoUrl } from "constants/runRoutes";
import { runRoutes, runRepoUrl } from "constants/runRoutes";
const useLastCheckedUpdateState = createPersistedState(
"__ROWY__RUN_LAST_CHECKED_UPDATE"
@@ -34,8 +34,8 @@ export default function RowyRun({
const handleVerify = async () => {
setVerified("LOADING");
try {
const versionReq = await fetch(inputRowyRunUrl + RunRoutes.version.path, {
method: RunRoutes.version.method,
const versionReq = await fetch(inputRowyRunUrl + runRoutes.version.path, {
method: runRoutes.version.method,
}).then((res) => res.json());
if (!versionReq.version) throw new Error("No version found");
@@ -62,8 +62,8 @@ export default function RowyRun({
);
const [version, setVersion] = useState("");
useEffect(() => {
fetch(settings.rowyRunUrl + RunRoutes.version.path, {
method: RunRoutes.version.method,
fetch(settings.rowyRunUrl + runRoutes.version.path, {
method: runRoutes.version.method,
})
.then((res) => res.json())
.then((data) => setVersion(data.version));
@@ -78,8 +78,8 @@ export default function RowyRun({
"/releases/latest";
try {
const versionReq = await fetch(
settings.rowyRunUrl + RunRoutes.version.path,
{ method: RunRoutes.version.method }
settings.rowyRunUrl + runRoutes.version.path,
{ method: runRoutes.version.method }
).then((res) => res.json());
const version = versionReq.version;
setVersion(version);

View File

@@ -0,0 +1,71 @@
import { useState } from "react";
import { Typography } from "@mui/material";
import LoadingButton, { LoadingButtonProps } from "@mui/lab/LoadingButton";
import { auth, googleProvider } from "@src/firebase";
export interface ISignInWithGoogleProps extends Partial<LoadingButtonProps> {
matchEmail?: string;
}
export default function SignInWithGoogle({
matchEmail,
...props
}: ISignInWithGoogleProps) {
const [status, setStatus] = useState<"IDLE" | "LOADING" | string>("IDLE");
const handleSignIn = async () => {
setStatus("LOADING");
try {
const result = await auth.signInWithPopup(googleProvider);
if (!result.user) throw new Error("Missing user");
if (
matchEmail &&
matchEmail.toLowerCase() !== result.user.email?.toLowerCase()
)
throw Error(`Account is not ${matchEmail}`);
setStatus("IDLE");
} catch (error: any) {
if (auth.currentUser) auth.signOut();
console.log(error);
setStatus(error.message);
}
};
return (
<div>
<LoadingButton
startIcon={
<img
src="https://www.gstatic.com/firebasejs/ui/2.0.0/images/auth/google.svg"
alt="Google logo"
width={20}
height={20}
style={{
margin: (24 - 20) / 2,
filter: props.disabled ? "grayscale(1)" : "",
}}
/>
}
onClick={handleSignIn}
loading={status === "LOADING"}
{...props}
>
Sign in with Google
</LoadingButton>
{status !== "LOADING" && status !== "IDLE" && (
<Typography
variant="caption"
color="error"
display="block"
sx={{ m: 0.5 }}
>
{status}
</Typography>
)}
</div>
);
}

View File

@@ -10,7 +10,8 @@ import OpenInNewIcon from "@mui/icons-material/OpenInNew";
import SetupItem from "./SetupItem";
import { name } from "@root/package.json";
import { runRepoUrl, RunRoutes } from "constants/runRoutes";
import { rowyRun } from "utils/rowyRun";
import { runRepoUrl, runRoutes } from "constants/runRoutes";
export default function Step1RowyRun({
completion,
@@ -28,15 +29,15 @@ export default function Step1RowyRun({
const [rowyRunUrl, setRowyRunUrl] = useState(paramsRowyRunUrl);
const [latestVersion, setLatestVersion] = useState("");
const [verificationStatus, setVerificationStatus] = useState<
"idle" | "loading" | "pass" | "fail"
>("idle");
"IDLE" | "LOADING" | "FAIL"
>("IDLE");
const verifyRowyRun = async () => {
setVerificationStatus("loading");
setVerificationStatus("LOADING");
try {
const result = await checkRowyRun(rowyRunUrl);
setVerificationStatus("pass");
setVerificationStatus("IDLE");
if (result.isValidRowyRunUrl) setIsValidRowyRunUrl(true);
@@ -51,8 +52,8 @@ export default function Step1RowyRun({
});
}
} catch (e: any) {
console.error(`Failed to verify Rowy Run URL: ${e.message}`);
setVerificationStatus("fail");
console.error(`Failed to verify Rowy Run URL: ${e}`);
setVerificationStatus("FAIL");
}
};
@@ -120,15 +121,15 @@ export default function Step1RowyRun({
type="url"
autoComplete="url"
fullWidth
error={verificationStatus === "fail"}
error={verificationStatus === "FAIL"}
helperText={
verificationStatus === "fail" ? "Invalid URL" : " "
verificationStatus === "FAIL" ? "Invalid URL" : " "
}
/>
<LoadingButton
variant="contained"
color="primary"
loading={verificationStatus === "loading"}
loading={verificationStatus === "LOADING"}
onClick={verifyRowyRun}
>
Verify
@@ -155,7 +156,7 @@ export default function Step1RowyRun({
<LoadingButton
variant="contained"
color="primary"
loading={verificationStatus === "loading"}
loading={verificationStatus === "LOADING"}
onClick={verifyRowyRun}
>
Verify
@@ -168,39 +169,43 @@ export default function Step1RowyRun({
);
}
export const checkRowyRun = async (rowyRunUrl: string) => {
export const checkRowyRun = async (
rowyRunUrl: string,
signal?: AbortSignal
) => {
const result = {
isValidRowyRunUrl: false,
isLatestVersion: false,
latestVersion: "",
};
const req = await fetch(rowyRunUrl + RunRoutes.version.path, {
method: RunRoutes.version.method,
});
if (!req.ok) return result;
const res = await req.json();
if (!res.version) return result;
try {
const res = await rowyRun({ rowyRunUrl, route: runRoutes.version, signal });
if (!res.version) return result;
result.isValidRowyRunUrl = true;
result.isValidRowyRunUrl = true;
// https://docs.github.com/en/rest/reference/repos#get-the-latest-release
const endpoint =
runRepoUrl.replace("github.com", "api.github.com/repos") +
"/releases/latest";
const latestVersionReq = await fetch(endpoint, {
headers: { Accept: "application/vnd.github.v3+json" },
});
const latestVersion = await latestVersionReq.json();
if (!latestVersion.tag_name) return result;
// https://docs.github.com/en/rest/reference/repos#get-the-latest-release
const endpoint =
runRepoUrl.replace("github.com", "api.github.com/repos") +
"/releases/latest";
const latestVersionReq = await fetch(endpoint, {
headers: { Accept: "application/vnd.github.v3+json" },
signal,
});
const latestVersion = await latestVersionReq.json();
if (!latestVersion.tag_name) return result;
if (latestVersion.tag_name > "v" + res.version) {
result.isLatestVersion = false;
result.latestVersion = latestVersion.tag_name;
} else {
result.isLatestVersion = true;
result.latestVersion = res.version;
if (latestVersion.tag_name > "v" + res.version) {
result.isLatestVersion = false;
result.latestVersion = latestVersion.tag_name;
} else {
result.isLatestVersion = true;
result.latestVersion = res.version;
}
} catch (e: any) {
console.error(e);
} finally {
return result;
}
return result;
};

View File

@@ -11,7 +11,8 @@ import SetupItem from "./SetupItem";
import { name } from "@root/package.json";
import { useAppContext } from "contexts/AppContext";
import { RunRoutes } from "constants/runRoutes";
import { rowyRun } from "utils/rowyRun";
import { runRoutes } from "constants/runRoutes";
export default function Step2ServiceAccount({
rowyRunUrl,
@@ -20,14 +21,14 @@ export default function Step2ServiceAccount({
}: ISetupStepBodyProps) {
const [hasAllRoles, setHasAllRoles] = useState(completion.serviceAccount);
const [verificationStatus, setVerificationStatus] = useState<
"idle" | "loading" | "pass" | "fail"
>("idle");
"IDLE" | "LOADING" | "FAIL"
>("IDLE");
const { projectId } = useAppContext();
const [region, setRegion] = useState("");
useEffect(() => {
fetch(rowyRunUrl + RunRoutes.region.path, {
method: RunRoutes.region.method,
fetch(rowyRunUrl + runRoutes.region.path, {
method: runRoutes.region.method,
})
.then((res) => res.json())
.then((data) => setRegion(data.region))
@@ -35,26 +36,26 @@ export default function Step2ServiceAccount({
}, []);
const verifyRoles = async () => {
setVerificationStatus("loading");
setVerificationStatus("LOADING");
try {
const result = await checkServiceAccount(rowyRunUrl);
if (result) {
setVerificationStatus("pass");
setVerificationStatus("IDLE");
setHasAllRoles(true);
setCompletion((c) => ({ ...c, serviceAccount: true }));
} else {
setVerificationStatus("fail");
setVerificationStatus("FAIL");
setHasAllRoles(false);
}
} catch (e) {
console.error(e);
setVerificationStatus("fail");
setVerificationStatus("FAIL");
}
};
return (
<>
<Typography variant="inherit">
<Typography variant="inherit" paragraph>
{name} Run uses the{" "}
<Link
href="https://firebase.google.com/docs/admin/setup"
@@ -79,8 +80,11 @@ export default function Step2ServiceAccount({
>
service account
</Link>
. Rowy Run operates exclusively on your GCP project and we will never
have access to your service account or any of your data.
.
</Typography>
<Typography variant="inherit" style={{ marginTop: 0 }}>
Rowy Run operates exclusively on your GCP project and we will never have
access to your service account or any of your data.
</Typography>
<SetupItem
@@ -91,7 +95,7 @@ export default function Step2ServiceAccount({
: "Set up a service account with the following IAM roles:"
}
>
<ul style={{ marginTop: 0 }}>
<ul>
<li>Service Account User required to deploy Cloud Functions</li>
<li>Firebase Authentication Admin</li>
<li>Firestore Service Agent</li>
@@ -113,12 +117,18 @@ export default function Step2ServiceAccount({
variant="contained"
color="primary"
onClick={verifyRoles}
loading={verificationStatus === "loading"}
loading={verificationStatus === "LOADING"}
>
Verify
</LoadingButton>
</Stack>
{verificationStatus === "FAIL" && (
<Typography variant="caption" color="error">
The service account does not have the required IAM roles.
</Typography>
)}
<Stack direction="row" spacing={1}>
<InfoIcon aria-label="Info" color="action" sx={{ mt: -0.25 }} />
<Typography variant="body2">
@@ -133,7 +143,6 @@ export default function Step2ServiceAccount({
href="https://cloud.google.com/iam/docs/understanding-roles"
target="_blank"
rel="noopener noreferrer"
variant="body2"
>
Learn about IAM roles
<InlineOpenInNewIcon />
@@ -143,15 +152,21 @@ export default function Step2ServiceAccount({
);
}
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;
export const checkServiceAccount = async (
rowyRunUrl: string,
signal?: AbortSignal
) => {
try {
const res = await rowyRun({
rowyRunUrl,
route: runRoutes.serviceAccountAccess,
});
return Object.values(res).reduce(
(acc, value) => acc && value,
true
) as boolean;
} catch (e: any) {
console.error(e);
return false;
}
};

View File

@@ -0,0 +1,177 @@
import { useState, useEffect } from "react";
import { ISetupStepBodyProps } from "pages/Setup";
import { Typography, Button } from "@mui/material";
import LoadingButton from "@mui/lab/LoadingButton";
import OpenInNewIcon from "@mui/icons-material/OpenInNew";
import SetupItem from "./SetupItem";
import SignInWithGoogle from "./SignInWithGoogle";
import { useAppContext } from "contexts/AppContext";
import { rowyRun } from "utils/rowyRun";
import { runRoutes } from "constants/runRoutes";
export default function Step3ProjectOwner({
rowyRunUrl,
completion,
setCompletion,
}: ISetupStepBodyProps) {
const { projectId, currentUser, authToken } = useAppContext();
const [email, setEmail] = useState("");
useEffect(() => {
rowyRun({ rowyRunUrl, route: runRoutes.projectOwner })
.then((data) => setEmail(data.email))
.catch((e: any) => {
console.error(e);
alert(`Failed to get project owner email: ${e.message}`);
});
}, [rowyRunUrl]);
const isSignedIn = currentUser?.email === email;
const [hasRoles, setHasRoles] = useState<boolean | "LOADING" | string>(
completion.projectOwner
);
const setRoles = async () => {
setHasRoles("LOADING");
try {
const res = await rowyRun({
route: runRoutes.setOwnerRoles,
rowyRunUrl,
authToken,
});
if (!res.success)
throw new Error(`${res.message}. Project owner: ${res.ownerEmail}`);
setHasRoles(true);
setCompletion((c) => ({ ...c, projectOwner: true }));
} catch (e: any) {
console.error(e);
setHasRoles(e.message);
}
};
return (
<>
<Typography variant="inherit">
The project owner requires the admin and owner roles to have full access
to manage this project. The default project owner is the Google Cloud
account used to deploy Rowy Run:{" "}
<b style={{ userSelect: "all" }}>{email}</b>
</Typography>
<SetupItem
status={isSignedIn ? "complete" : "incomplete"}
title={
isSignedIn
? "Firebase Authentication is set up."
: "Check that Firebase Authentication is set up with:"
}
>
{!isSignedIn && (
<>
<ol>
<li>the Google auth provider enabled and</li>
<li>
this domain authorized:{" "}
<b style={{ userSelect: "all" }}>{window.location.hostname}</b>
</li>
</ol>
<Button
href={`https://console.firebase.google.com/project/${
projectId || "_"
}/authentication/providers`}
target="_blank"
rel="noopener noreferrer"
endIcon={<OpenInNewIcon />}
>
Set Up in Firebase Console
</Button>
</>
)}
</SetupItem>
<SetupItem
status={isSignedIn ? "complete" : "incomplete"}
title={
isSignedIn ? (
`Youre signed in as the project owner.`
) : (
<>
Sign in as the project owner: <b>{email}</b>
</>
)
}
>
{!isSignedIn && (
<SignInWithGoogle
matchEmail={email}
loading={!email ? true : undefined}
/>
)}
</SetupItem>
{isSignedIn && (
<SetupItem
status={hasRoles === true ? "complete" : "incomplete"}
title={
hasRoles === true
? "The project owner has the admin and owner roles."
: "Assign the admin and owner roles to the project owner."
}
>
{hasRoles !== true && (
<div>
<LoadingButton
variant="contained"
color="primary"
loading={hasRoles === "LOADING"}
onClick={setRoles}
>
Assign Roles
</LoadingButton>
{typeof hasRoles === "string" && hasRoles !== "LOADING" && (
<Typography
variant="caption"
color="error"
display="block"
sx={{ mt: 0.5 }}
>
{hasRoles}
</Typography>
)}
</div>
)}
</SetupItem>
)}
</>
);
}
export const checkProjectOwner = async (
rowyRunUrl: string,
currentUser: firebase.default.User | null | undefined,
userRoles: string[] | null,
signal?: AbortSignal
) => {
if (!currentUser || !Array.isArray(userRoles)) return false;
try {
const res = await rowyRun({
rowyRunUrl,
route: runRoutes.projectOwner,
signal,
});
const email = res.email;
if (currentUser.email !== email) return false;
return userRoles.includes("ADMIN") && userRoles.includes("OWNER");
} catch (e: any) {
console.error(e);
return false;
}
};

View File

@@ -23,7 +23,7 @@ import Button from "@mui/material/Button";
import routes from "constants/routes";
import { SETTINGS } from "config/dbPaths";
import { name as appName } from "@root/package.json";
import { RunRoutes } from "@src/constants/runRoutes";
import { runRoutes } from "@src/constants/runRoutes";
export default function FieldSettings(props: IMenuModalProps) {
const { name, fieldName, type, open, config, handleClose, handleSave } =
@@ -126,7 +126,7 @@ export default function FieldSettings(props: IMenuModalProps) {
handleConfirm: async () => {
if (!rowyRun) return;
rowyRun({
route: RunRoutes.buildFunction,
route: runRoutes.buildFunction,
body: {
tablePath: tableState?.tablePath,
pathname: window.location.pathname,

View File

@@ -19,7 +19,7 @@ import { useSnackLogContext } from "contexts/SnackLogContext";
import { emptyExtensionObject, IExtension, IExtensionType } from "./utils";
import { name } from "@root/package.json";
import { RunRoutes } from "@src/constants/runRoutes";
import { runRoutes } from "@src/constants/runRoutes";
import { analytics } from "@src/analytics";
export default function ExtensionsEditor() {
@@ -88,7 +88,7 @@ export default function ExtensionsEditor() {
snackLogContext.requestSnackLog();
if (rowyRun)
rowyRun({
route: RunRoutes.buildFunction,
route: runRoutes.buildFunction,
body: {
tablePath: tableState?.tablePath,
pathname: window.location.pathname,

View File

@@ -12,7 +12,7 @@ import { cloudFunction } from "firebase/callables";
import { formatPath } from "utils/fns";
import { useConfirmation } from "components/ConfirmationDialog";
import { useActionParams } from "./FormDialog/Context";
import { RunRoutes } from "@src/constants/runRoutes";
import { runRoutes } from "@src/constants/runRoutes";
const replacer = (data: any) => (m: string, key: string) => {
const objKey = key.split(":")[0];
@@ -78,7 +78,7 @@ export default function ActionFab({
};
const resp = await rowyRun({
route: RunRoutes.actionScript,
route: runRoutes.actionScript,
body: data,
params: [],
});

View File

@@ -29,9 +29,9 @@ type actionScriptRequest = {
body: ActionData;
};
type RunRoutes = actionScriptRequest | impersonateUserRequest;
export type runRouteRequest = actionScriptRequest | impersonateUserRequest;
export const RunRoutes: { [key: string]: RunRoute } = {
export const runRoutes: Record<string, RunRoute> = {
impersonateUser: { path: "/impersonateUser", method: "GET" },
version: { path: "/version", method: "GET" },
region: { path: "/region", method: "GET" },

View File

@@ -9,7 +9,7 @@ import FirebaseUi from "components/Auth/FirebaseUi";
import { signOut } from "utils/auth";
import { auth } from "../../firebase";
import { useProjectContext } from "@src/contexts/ProjectContext";
import { RunRoutes } from "@src/constants/runRoutes";
import { runRoutes } from "@src/constants/runRoutes";
import { name } from "@root/package.json";
export default function ImpersonatorAuthPage() {
@@ -32,7 +32,7 @@ export default function ImpersonatorAuthPage() {
console.log("rowyRun");
setLoading(true);
const resp = await rowyRun({
route: RunRoutes.impersonateUser,
route: runRoutes.impersonateUser,
params: [email],
});
console.log(resp);

View File

@@ -18,7 +18,7 @@ import {
} from "@mui/material";
import { useConfirmation } from "components/ConfirmationDialog";
import { useProjectContext } from "@src/contexts/ProjectContext";
import { RunRoutes } from "@src/constants/runRoutes";
import { runRoutes } from "@src/constants/runRoutes";
const useBodyCacheState = createPersistedState("__ROWY__RR_TEST_REQ_BODY");
export default function TestView() {
@@ -37,7 +37,7 @@ export default function TestView() {
const handleMethodChange = (_, newMethod) => setMethod(newMethod);
const setDefinedRoute = (newPath) => {
setPath(newPath.target.value);
const _method = Object.values(RunRoutes).find(
const _method = Object.values(runRoutes).find(
(r) => r.path === path
)?.method;
if (_method) {
@@ -88,12 +88,12 @@ export default function TestView() {
label="Defined Route"
select
value={
Object.values(RunRoutes).find((r) => r.path === path)?.path ?? ""
Object.values(runRoutes).find((r) => r.path === path)?.path ?? ""
}
onChange={setDefinedRoute}
style={{ width: 255 }}
>
{Object.values(RunRoutes).map((route) => (
{Object.values(runRoutes).map((route) => (
<MenuItem key={route.path} value={route.path}>
{route.path}
</MenuItem>

View File

@@ -19,6 +19,7 @@ import {
Tooltip,
} from "@mui/material";
import { alpha } from "@mui/material/styles";
import LoadingButton from "@mui/lab/LoadingButton";
import ChevronLeftIcon from "@mui/icons-material/ChevronLeft";
import ChevronRightIcon from "@mui/icons-material/ChevronRight";
@@ -31,9 +32,12 @@ import Step0Welcome from "components/Setup/Step0Welcome";
import Step1RowyRun, { checkRowyRun } from "components/Setup/Step1RowyRun";
// prettier-ignore
import Step2ServiceAccount, { checkServiceAccount } from "components/Setup/Step2ServiceAccount";
// prettier-ignore
import Step3ProjectOwner, { checkProjectOwner } from "@src/components/Setup/Step3ProjectOwner";
import { name } from "@root/package.json";
import routes from "constants/routes";
import { useAppContext } from "contexts/AppContext";
export interface ISetupStep {
id: string;
@@ -54,24 +58,35 @@ export interface ISetupStepBodyProps {
const checkAllSteps = async (
rowyRunUrl: string,
setCompletion: React.Dispatch<React.SetStateAction<Record<string, boolean>>>
currentUser: firebase.default.User | null | undefined,
userRoles: string[] | null,
signal: AbortSignal
) => {
console.log("Check all steps");
const completion: Record<string, boolean> = {};
const rowyRunValidation = await checkRowyRun(rowyRunUrl);
const rowyRunValidation = await checkRowyRun(rowyRunUrl, signal);
if (rowyRunValidation.isValidRowyRunUrl) {
if (rowyRunValidation.isLatestVersion) completion.rowyRun = true;
const serviceAccount = await checkServiceAccount(rowyRunUrl);
const serviceAccount = await checkServiceAccount(rowyRunUrl, signal);
if (serviceAccount) completion.serviceAccount = true;
const projectOwner = await checkProjectOwner(
rowyRunUrl,
currentUser,
userRoles,
signal
);
if (projectOwner) completion.projectOwner = true;
}
if (Object.keys(completion).length > 0)
setCompletion((c) => ({ ...c, ...completion }));
return completion;
};
export default function SetupPage() {
const { currentUser, userRoles } = useAppContext();
const fullScreenHeight = use100vh() ?? 0;
const isMobile = useMediaQuery((theme: any) => theme.breakpoints.down("sm"));
@@ -84,13 +99,27 @@ export default function SetupPage() {
welcome: false,
rowyRun: false,
serviceAccount: false,
signIn: false,
projectOwner: false,
rules: false,
});
const [checkingAllSteps, setCheckingAllSteps] = useState(false);
useEffect(() => {
if (rowyRunUrl) checkAllSteps(rowyRunUrl, setCompletion);
}, [rowyRunUrl]);
const controller = new AbortController();
const signal = controller.signal;
if (rowyRunUrl) {
setCheckingAllSteps(true);
checkAllSteps(rowyRunUrl, currentUser, userRoles, signal).then(
(result) => {
if (!signal.aborted) setCompletion((c) => ({ ...c, ...result }));
setCheckingAllSteps(false);
}
);
}
return () => controller.abort();
}, [rowyRunUrl, currentUser, userRoles]);
const stepProps = { completion, setCompletion, checkAllSteps, rowyRunUrl };
@@ -102,15 +131,25 @@ export default function SetupPage() {
title: `Welcome to ${name}`,
body: <Step0Welcome {...stepProps} />,
actions: completion.welcome ? (
<Button variant="contained" color="primary" type="submit">
<LoadingButton
loading={checkingAllSteps}
variant="contained"
color="primary"
type="submit"
>
Get Started
</Button>
</LoadingButton>
) : (
<Tooltip title="Please accept the terms and conditions">
<div>
<Button variant="contained" color="primary" disabled>
<LoadingButton
loading={checkingAllSteps}
variant="contained"
color="primary"
disabled
>
Get Started
</Button>
</LoadingButton>
</div>
</Tooltip>
),
@@ -128,25 +167,22 @@ export default function SetupPage() {
body: <Step2ServiceAccount {...stepProps} />,
},
{
id: "signIn",
shortTitle: `Sign In`,
title: `Sign In as the Project Owner`,
description: `${name} Run is a Google Cloud Run instance that provides back-end functionality, such as table action scripts, user management, and easy Cloud Functions deployment. Learn more`,
body: `x`,
id: "projectOwner",
shortTitle: `Project Owner`,
title: `Set Up Project Owner`,
body: <Step3ProjectOwner {...stepProps} />,
},
{
id: "rules",
shortTitle: `Rules`,
title: `Set Up Firestore Rules`,
description: `${name} Run is a Google Cloud Run instance that provides back-end functionality, such as table action scripts, user management, and easy Cloud Functions deployment. Learn more`,
body: `x`,
},
completion.migrate !== undefined
? {
id: "migrate",
shortTitle: `Migrate`,
title: `Migrate to ${name}`,
description: `${name} Run is a Google Cloud Run instance that provides back-end functionality, such as table action scripts, user management, and easy Cloud Functions deployment. Learn more`,
title: `Migrate to ${name} (optional)`,
body: `x`,
}
: ({} as ISetupStep),
@@ -358,14 +394,15 @@ export default function SetupPage() {
>
<DialogActions>
{step.actions ?? (
<Button
<LoadingButton
variant="contained"
color="primary"
type="submit"
loading={checkingAllSteps}
disabled={!completion[stepId]}
>
Continue
</Button>
</LoadingButton>
)}
</DialogActions>
</form>

View File

@@ -8,6 +8,7 @@ export interface IRowyRunRequestProps {
params?: string[];
localhost?: boolean;
json?: boolean;
signal?: AbortSignal;
}
export const rowyRun = async ({
@@ -18,6 +19,7 @@ export const rowyRun = async ({
params,
localhost = false,
json = true,
signal,
}: IRowyRunRequestProps) => {
const { method, path } = route;
let url = (localhost ? "http://localhost:8080" : rowyRunUrl) + path;
@@ -35,6 +37,7 @@ export const rowyRun = async ({
redirect: "follow",
referrerPolicy: "no-referrer",
body: body && method !== "GET" ? JSON.stringify(body) : null, // body data type must match "Content-Type" header
signal,
});
if (json) return await response.json();