mirror of
https://github.com/rowyio/rowy.git
synced 2025-12-29 00:16:39 +01:00
Merge branch 'v2' of https://github.com/notsidney/xtable into v2
This commit is contained in:
@@ -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);
|
||||
|
||||
71
src/components/Setup/SignInWithGoogle.tsx
Normal file
71
src/components/Setup/SignInWithGoogle.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
177
src/components/Setup/Step3ProjectOwner.tsx
Normal file
177
src/components/Setup/Step3ProjectOwner.tsx
Normal 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 ? (
|
||||
`You’re 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;
|
||||
}
|
||||
};
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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: [],
|
||||
});
|
||||
|
||||
@@ -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" },
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user