|
|
@@ -1,57 +1,58 @@
|
|
|
import React, { useCallback, useContext, useEffect, useMemo } from "react";
|
|
|
-import { zodResolver } from "@hookform/resolvers/zod";
|
|
|
-import { type PorterApp } from "@porter-dev/api-contracts";
|
|
|
-import { useQuery } from "@tanstack/react-query";
|
|
|
-import axios from "axios";
|
|
|
+import { RouteComponentProps, withRouter } from "react-router";
|
|
|
+import web from "assets/web.png";
|
|
|
import AnimateHeight from "react-animate-height";
|
|
|
-import { Controller, FormProvider, useForm } from "react-hook-form";
|
|
|
-import { withRouter, type RouteComponentProps } from "react-router";
|
|
|
-import styled from "styled-components";
|
|
|
-import { z } from "zod";
|
|
|
+import axios from "axios";
|
|
|
+import { zodResolver } from "@hookform/resolvers/zod";
|
|
|
|
|
|
+import styled from "styled-components";
|
|
|
+import { useForm, Controller, FormProvider } from "react-hook-form";
|
|
|
import Back from "components/porter/Back";
|
|
|
-import Button from "components/porter/Button";
|
|
|
-import Container from "components/porter/Container";
|
|
|
+import VerticalSteps from "components/porter/VerticalSteps";
|
|
|
+import Text from "components/porter/Text";
|
|
|
+import Spacer from "components/porter/Spacer";
|
|
|
import { ControlledInput } from "components/porter/ControlledInput";
|
|
|
-import Error from "components/porter/Error";
|
|
|
import Link from "components/porter/Link";
|
|
|
-import Spacer from "components/porter/Spacer";
|
|
|
-import Text from "components/porter/Text";
|
|
|
-import VerticalSteps from "components/porter/VerticalSteps";
|
|
|
-import DashboardHeader from "main/home/cluster-dashboard/DashboardHeader";
|
|
|
-import { useAppAnalytics } from "lib/hooks/useAppAnalytics";
|
|
|
-import { useAppValidation } from "lib/hooks/useAppValidation";
|
|
|
-import { useDefaultDeploymentTarget } from "lib/hooks/useDeploymentTarget";
|
|
|
-import { useIntercom } from "lib/hooks/useIntercom";
|
|
|
-import { usePorterYaml } from "lib/hooks/usePorterYaml";
|
|
|
+
|
|
|
+import { Context } from "shared/Context";
|
|
|
import {
|
|
|
+ PorterAppFormData,
|
|
|
+ SourceOptions,
|
|
|
porterAppFormValidator,
|
|
|
- type PorterAppFormData,
|
|
|
- type SourceOptions,
|
|
|
+ clientAppValidator,
|
|
|
} from "lib/porter-apps";
|
|
|
+import DashboardHeader from "main/home/cluster-dashboard/DashboardHeader";
|
|
|
+import SourceSelector from "../new-app-flow/SourceSelector";
|
|
|
+import Button from "components/porter/Button";
|
|
|
+import RepoSettings from "./RepoSettings";
|
|
|
+import Container from "components/porter/Container";
|
|
|
+import ServiceList from "../validate-apply/services-settings/ServiceList";
|
|
|
import {
|
|
|
defaultSerialized,
|
|
|
deserializeService,
|
|
|
} from "lib/porter-apps/services";
|
|
|
-
|
|
|
-import api from "shared/api";
|
|
|
-import { useClusterResources } from "shared/ClusterResourcesContext";
|
|
|
-import { Context } from "shared/Context";
|
|
|
+import { usePorterYaml } from "lib/hooks/usePorterYaml";
|
|
|
import { valueExists } from "shared/util";
|
|
|
-import web from "assets/web.png";
|
|
|
-import ImageSettings from "../image-settings/ImageSettings";
|
|
|
+import api from "shared/api";
|
|
|
+import { PorterApp } from "@porter-dev/api-contracts";
|
|
|
import GithubActionModal from "../new-app-flow/GithubActionModal";
|
|
|
-import SourceSelector from "../new-app-flow/SourceSelector";
|
|
|
-import EnvSettings from "../validate-apply/app-settings/EnvSettings";
|
|
|
+import { useDefaultDeploymentTarget } from "lib/hooks/useDeploymentTarget";
|
|
|
+import Error from "components/porter/Error";
|
|
|
+import { useAppAnalytics } from "lib/hooks/useAppAnalytics";
|
|
|
+import { useAppValidation } from "lib/hooks/useAppValidation";
|
|
|
+import { useQuery } from "@tanstack/react-query";
|
|
|
+import { z } from "zod";
|
|
|
import {
|
|
|
+ PopulatedEnvGroup,
|
|
|
populatedEnvGroup,
|
|
|
- type PopulatedEnvGroup,
|
|
|
} from "../validate-apply/app-settings/types";
|
|
|
-import ServiceList from "../validate-apply/services-settings/ServiceList";
|
|
|
+import EnvSettings from "../validate-apply/app-settings/EnvSettings";
|
|
|
+import ImageSettings from "../image-settings/ImageSettings";
|
|
|
+import { useClusterResources } from "shared/ClusterResourcesContext";
|
|
|
import PorterYamlModal from "./PorterYamlModal";
|
|
|
-import RepoSettings from "./RepoSettings";
|
|
|
+import { useIntercom } from "lib/hooks/useIntercom";
|
|
|
|
|
|
-type CreateAppProps = RouteComponentProps;
|
|
|
+type CreateAppProps = {} & RouteComponentProps;
|
|
|
|
|
|
const CreateApp: React.FC<CreateAppProps> = ({ history }) => {
|
|
|
const { currentProject, currentCluster } = useContext(Context);
|
|
|
@@ -72,8 +73,10 @@ const CreateApp: React.FC<CreateAppProps> = ({ history }) => {
|
|
|
|
|
|
const { showIntercomWithMessage } = useIntercom();
|
|
|
|
|
|
- const [validatedAppProto, setValidatedAppProto] =
|
|
|
- React.useState<PorterApp | null>(null);
|
|
|
+ const [
|
|
|
+ validatedAppProto,
|
|
|
+ setValidatedAppProto,
|
|
|
+ ] = React.useState<PorterApp | null>(null);
|
|
|
const [isDeploying, setIsDeploying] = React.useState(false);
|
|
|
const [deployError, setDeployError] = React.useState("");
|
|
|
const [{ variables, secrets }, setFinalizedAppEnv] = React.useState<{
|
|
|
@@ -88,7 +91,7 @@ const CreateApp: React.FC<CreateAppProps> = ({ history }) => {
|
|
|
["getPorterApps", currentProject?.id, currentCluster?.id],
|
|
|
async () => {
|
|
|
if (!currentProject?.id || !currentCluster?.id) {
|
|
|
- return await Promise.resolve([]);
|
|
|
+ return Promise.resolve([]);
|
|
|
}
|
|
|
|
|
|
const res = await api.getPorterApps(
|
|
|
@@ -128,13 +131,13 @@ const CreateApp: React.FC<CreateAppProps> = ({ history }) => {
|
|
|
}
|
|
|
);
|
|
|
|
|
|
- const { environment_groups: environmentGroups } = await z
|
|
|
+ const { environment_groups } = await z
|
|
|
.object({
|
|
|
environment_groups: z.array(populatedEnvGroup).default([]),
|
|
|
})
|
|
|
.parseAsync(res.data);
|
|
|
|
|
|
- return environmentGroups;
|
|
|
+ return environment_groups;
|
|
|
}
|
|
|
);
|
|
|
|
|
|
@@ -156,7 +159,7 @@ const CreateApp: React.FC<CreateAppProps> = ({ history }) => {
|
|
|
env: [],
|
|
|
efsStorage: {
|
|
|
enabled: false,
|
|
|
- },
|
|
|
+ }
|
|
|
},
|
|
|
source: {
|
|
|
git_repo_name: "",
|
|
|
@@ -204,7 +207,7 @@ const CreateApp: React.FC<CreateAppProps> = ({ history }) => {
|
|
|
});
|
|
|
const { currentClusterResources } = useClusterResources();
|
|
|
|
|
|
- const resetAllExceptName = (): void => {
|
|
|
+ const resetAllExceptName = () => {
|
|
|
setIsNameHighlight(true);
|
|
|
|
|
|
// Get the current name value before the reset
|
|
|
@@ -259,10 +262,7 @@ const CreateApp: React.FC<CreateAppProps> = ({ history }) => {
|
|
|
}) => {
|
|
|
setIsDeploying(true);
|
|
|
// log analytics event that we started form submission
|
|
|
- void updateAppStep({
|
|
|
- step: "stack-launch-complete",
|
|
|
- appName: name.value,
|
|
|
- });
|
|
|
+ updateAppStep({ step: "stack-launch-complete", appName: name.value });
|
|
|
|
|
|
try {
|
|
|
if (!currentProject?.id || !currentCluster?.id) {
|
|
|
@@ -278,7 +278,7 @@ const CreateApp: React.FC<CreateAppProps> = ({ history }) => {
|
|
|
{
|
|
|
...source,
|
|
|
name: app.name,
|
|
|
- deployment_target_id: deploymentTarget.deployment_target_id,
|
|
|
+ deployment_target_id: deploymentTarget.deployment_target_id
|
|
|
},
|
|
|
{
|
|
|
project_id: currentProject.id,
|
|
|
@@ -302,10 +302,7 @@ const CreateApp: React.FC<CreateAppProps> = ({ history }) => {
|
|
|
);
|
|
|
|
|
|
// log analytics event that we successfully deployed
|
|
|
- void updateAppStep({
|
|
|
- step: "stack-launch-success",
|
|
|
- appName: name.value,
|
|
|
- });
|
|
|
+ updateAppStep({ step: "stack-launch-success", appName: name.value });
|
|
|
|
|
|
if (source.type === "docker-registry") {
|
|
|
history.push(`/apps/${app.name}`);
|
|
|
@@ -318,7 +315,7 @@ const CreateApp: React.FC<CreateAppProps> = ({ history }) => {
|
|
|
});
|
|
|
|
|
|
if (axios.isAxiosError(err) && err.response?.data?.error) {
|
|
|
- void updateAppStep({
|
|
|
+ updateAppStep({
|
|
|
step: "stack-launch-failure",
|
|
|
errorMessage: err.response?.data?.error,
|
|
|
appName: name.value,
|
|
|
@@ -329,7 +326,7 @@ const CreateApp: React.FC<CreateAppProps> = ({ history }) => {
|
|
|
|
|
|
const msg =
|
|
|
"An error occurred while deploying your application. Please try again.";
|
|
|
- void updateAppStep({
|
|
|
+ updateAppStep({
|
|
|
step: "stack-launch-failure",
|
|
|
errorMessage: msg,
|
|
|
appName: name.value,
|
|
|
@@ -340,7 +337,7 @@ const CreateApp: React.FC<CreateAppProps> = ({ history }) => {
|
|
|
setIsDeploying(false);
|
|
|
}
|
|
|
},
|
|
|
- [currentProject?.id, currentCluster?.id, deploymentTarget, name.value]
|
|
|
+ [currentProject?.id, currentCluster?.id, deploymentTarget]
|
|
|
);
|
|
|
|
|
|
useEffect(() => {
|
|
|
@@ -361,7 +358,7 @@ const CreateApp: React.FC<CreateAppProps> = ({ history }) => {
|
|
|
|
|
|
// set step to 2 if source is filled out
|
|
|
if (source?.type && source.type === "docker-registry") {
|
|
|
- if (image?.tag) {
|
|
|
+ if (image && image.tag) {
|
|
|
setStep((prev) => Math.max(prev, 2));
|
|
|
}
|
|
|
}
|
|
|
@@ -415,7 +412,7 @@ const CreateApp: React.FC<CreateAppProps> = ({ history }) => {
|
|
|
message: "I am running into an issue launching an application.",
|
|
|
});
|
|
|
|
|
|
- void updateAppStep({
|
|
|
+ updateAppStep({
|
|
|
step: "stack-launch-failure",
|
|
|
errorMessage: `Form validation error: ${errorMessage}`,
|
|
|
appName: name.value,
|
|
|
@@ -423,6 +420,8 @@ const CreateApp: React.FC<CreateAppProps> = ({ history }) => {
|
|
|
|
|
|
return <Error message={errorMessage} maxWidth="600px" />;
|
|
|
}
|
|
|
+
|
|
|
+ return;
|
|
|
}, [isValidating, isDeploying, deployError, errors]);
|
|
|
|
|
|
const submitDisabled = useMemo(() => {
|
|
|
@@ -513,11 +512,7 @@ const CreateApp: React.FC<CreateAppProps> = ({ history }) => {
|
|
|
/>
|
|
|
<DarkMatter />
|
|
|
<FormProvider {...porterAppFormMethods}>
|
|
|
- <form
|
|
|
- onSubmit={() => {
|
|
|
- void onSubmit();
|
|
|
- }}
|
|
|
- >
|
|
|
+ <form onSubmit={onSubmit}>
|
|
|
<VerticalSteps
|
|
|
currentStep={step}
|
|
|
steps={[
|
|
|
@@ -525,7 +520,7 @@ const CreateApp: React.FC<CreateAppProps> = ({ history }) => {
|
|
|
<Text size={16}>Application name</Text>
|
|
|
<Spacer y={0.5} />
|
|
|
<Text color={isNameHighlight ? "#FFCC00" : "helper"}>
|
|
|
- Lowercase letters, numbers, and "-" only.
|
|
|
+ Lowercase letters, numbers, and "-" only.
|
|
|
</Text>
|
|
|
<Spacer y={0.5} />
|
|
|
<ControlledInput
|
|
|
@@ -585,11 +580,11 @@ const CreateApp: React.FC<CreateAppProps> = ({ history }) => {
|
|
|
control={control}
|
|
|
render={({ field: { onChange, value } }) => (
|
|
|
<PorterYamlModal
|
|
|
- close={() => {
|
|
|
+ close={() =>
|
|
|
setUserHasSeenNoPorterYamlFoundModal(
|
|
|
true
|
|
|
- );
|
|
|
- }}
|
|
|
+ )
|
|
|
+ }
|
|
|
setPorterYamlPath={(porterYamlPath) => {
|
|
|
onChange(porterYamlPath);
|
|
|
}}
|
|
|
@@ -612,23 +607,23 @@ const CreateApp: React.FC<CreateAppProps> = ({ history }) => {
|
|
|
<ImageSettings
|
|
|
projectId={currentProject.id}
|
|
|
imageUri={image?.repository ?? ""}
|
|
|
- setImageUri={(uri: string) => {
|
|
|
+ setImageUri={(uri: string) =>
|
|
|
setValue("source.image", {
|
|
|
...image,
|
|
|
repository: uri,
|
|
|
- });
|
|
|
- }}
|
|
|
+ })
|
|
|
+ }
|
|
|
imageTag={image?.tag ?? ""}
|
|
|
- setImageTag={(tag: string) => {
|
|
|
- setValue("source.image", { ...image, tag });
|
|
|
- }}
|
|
|
- resetImageInfo={() => {
|
|
|
+ setImageTag={(tag: string) =>
|
|
|
+ setValue("source.image", { ...image, tag })
|
|
|
+ }
|
|
|
+ resetImageInfo={() =>
|
|
|
setValue("source.image", {
|
|
|
...image,
|
|
|
repository: "",
|
|
|
tag: "",
|
|
|
- });
|
|
|
- }}
|
|
|
+ })
|
|
|
+ }
|
|
|
/>
|
|
|
)
|
|
|
) : null}
|
|
|
@@ -654,9 +649,8 @@ const CreateApp: React.FC<CreateAppProps> = ({ history }) => {
|
|
|
}
|
|
|
>
|
|
|
{detectedServices.count > 0
|
|
|
- ? `Detected ${detectedServices.count} service${
|
|
|
- detectedServices.count > 1 ? "s" : ""
|
|
|
- } from porter.yaml.`
|
|
|
+ ? `Detected ${detectedServices.count} service${detectedServices.count > 1 ? "s" : ""
|
|
|
+ } from porter.yaml.`
|
|
|
: `Could not detect any services from porter.yaml. Make sure it exists in the root of your repo.`}
|
|
|
</Text>
|
|
|
</AppearingDiv>
|
|
|
@@ -702,17 +696,15 @@ const CreateApp: React.FC<CreateAppProps> = ({ history }) => {
|
|
|
/>
|
|
|
</>
|
|
|
),
|
|
|
- <>
|
|
|
- <Button
|
|
|
- type="submit"
|
|
|
- status={submitBtnStatus}
|
|
|
- loadingText={"Deploying..."}
|
|
|
- width={"120px"}
|
|
|
- disabled={submitDisabled}
|
|
|
- >
|
|
|
- Deploy app
|
|
|
- </Button>
|
|
|
- </>,
|
|
|
+ <Button
|
|
|
+ type="submit"
|
|
|
+ status={submitBtnStatus}
|
|
|
+ loadingText={"Deploying..."}
|
|
|
+ width={"120px"}
|
|
|
+ disabled={submitDisabled}
|
|
|
+ >
|
|
|
+ Deploy app
|
|
|
+ </Button>,
|
|
|
].filter((x) => x)}
|
|
|
/>
|
|
|
</form>
|
|
|
@@ -722,9 +714,7 @@ const CreateApp: React.FC<CreateAppProps> = ({ history }) => {
|
|
|
</Div>
|
|
|
{showGHAModal && source?.type === "github" && (
|
|
|
<GithubActionModal
|
|
|
- closeModal={() => {
|
|
|
- setShowGHAModal(false);
|
|
|
- }}
|
|
|
+ closeModal={() => setShowGHAModal(false)}
|
|
|
githubAppInstallationID={source.git_repo_id}
|
|
|
githubRepoOwner={source.git_repo_name.split("/")[0]}
|
|
|
githubRepoName={source.git_repo_name.split("/")[1]}
|
|
|
@@ -732,8 +722,8 @@ const CreateApp: React.FC<CreateAppProps> = ({ history }) => {
|
|
|
stackName={name.value}
|
|
|
projectId={currentProject.id}
|
|
|
clusterId={currentCluster.id}
|
|
|
- deployPorterApp={async () =>
|
|
|
- await createAndApply({
|
|
|
+ deployPorterApp={() =>
|
|
|
+ createAndApply({
|
|
|
app: validatedAppProto,
|
|
|
source,
|
|
|
variables,
|
|
|
@@ -793,7 +783,7 @@ const AppearingDiv = styled.div<{ color?: string }>`
|
|
|
animation-fill-mode: forwards;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
- color: ${(props) => props.color ?? "#ffffff44"};
|
|
|
+ color: ${(props) => props.color || "#ffffff44"};
|
|
|
margin-left: 10px;
|
|
|
@keyframes floatIn {
|
|
|
from {
|