|
|
@@ -1,58 +1,57 @@
|
|
|
import React, { useCallback, useContext, useEffect, useMemo } from "react";
|
|
|
-import { RouteComponentProps, withRouter } from "react-router";
|
|
|
-import web from "assets/web.png";
|
|
|
-import AnimateHeight from "react-animate-height";
|
|
|
-import axios from "axios";
|
|
|
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 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 { useForm, Controller, FormProvider } from "react-hook-form";
|
|
|
+import { z } from "zod";
|
|
|
+
|
|
|
import Back from "components/porter/Back";
|
|
|
-import VerticalSteps from "components/porter/VerticalSteps";
|
|
|
-import Text from "components/porter/Text";
|
|
|
-import Spacer from "components/porter/Spacer";
|
|
|
+import Button from "components/porter/Button";
|
|
|
+import Container from "components/porter/Container";
|
|
|
import { ControlledInput } from "components/porter/ControlledInput";
|
|
|
+import Error from "components/porter/Error";
|
|
|
import Link from "components/porter/Link";
|
|
|
-
|
|
|
-import { Context } from "shared/Context";
|
|
|
+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 {
|
|
|
- PorterAppFormData,
|
|
|
- SourceOptions,
|
|
|
porterAppFormValidator,
|
|
|
- clientAppValidator,
|
|
|
+ type PorterAppFormData,
|
|
|
+ type SourceOptions,
|
|
|
} 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 { usePorterYaml } from "lib/hooks/usePorterYaml";
|
|
|
-import { valueExists } from "shared/util";
|
|
|
+
|
|
|
import api from "shared/api";
|
|
|
-import { PorterApp } from "@porter-dev/api-contracts";
|
|
|
+import { useClusterResources } from "shared/ClusterResourcesContext";
|
|
|
+import { Context } from "shared/Context";
|
|
|
+import { valueExists } from "shared/util";
|
|
|
+import web from "assets/web.png";
|
|
|
+import ImageSettings from "../image-settings/ImageSettings";
|
|
|
import GithubActionModal from "../new-app-flow/GithubActionModal";
|
|
|
-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 SourceSelector from "../new-app-flow/SourceSelector";
|
|
|
+import EnvSettings from "../validate-apply/app-settings/EnvSettings";
|
|
|
import {
|
|
|
- PopulatedEnvGroup,
|
|
|
populatedEnvGroup,
|
|
|
+ type PopulatedEnvGroup,
|
|
|
} from "../validate-apply/app-settings/types";
|
|
|
-import EnvSettings from "../validate-apply/app-settings/EnvSettings";
|
|
|
-import ImageSettings from "../image-settings/ImageSettings";
|
|
|
-import { useClusterResources } from "shared/ClusterResourcesContext";
|
|
|
+import ServiceList from "../validate-apply/services-settings/ServiceList";
|
|
|
import PorterYamlModal from "./PorterYamlModal";
|
|
|
-import { useIntercom } from "lib/hooks/useIntercom";
|
|
|
+import RepoSettings from "./RepoSettings";
|
|
|
|
|
|
-type CreateAppProps = {} & RouteComponentProps;
|
|
|
+type CreateAppProps = RouteComponentProps;
|
|
|
|
|
|
const CreateApp: React.FC<CreateAppProps> = ({ history }) => {
|
|
|
const { currentProject, currentCluster } = useContext(Context);
|
|
|
@@ -73,10 +72,8 @@ 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<{
|
|
|
@@ -91,7 +88,7 @@ const CreateApp: React.FC<CreateAppProps> = ({ history }) => {
|
|
|
["getPorterApps", currentProject?.id, currentCluster?.id],
|
|
|
async () => {
|
|
|
if (!currentProject?.id || !currentCluster?.id) {
|
|
|
- return Promise.resolve([]);
|
|
|
+ return await Promise.resolve([]);
|
|
|
}
|
|
|
|
|
|
const res = await api.getPorterApps(
|
|
|
@@ -131,13 +128,13 @@ const CreateApp: React.FC<CreateAppProps> = ({ history }) => {
|
|
|
}
|
|
|
);
|
|
|
|
|
|
- const { environment_groups } = await z
|
|
|
+ const { environment_groups: environmentGroups } = await z
|
|
|
.object({
|
|
|
environment_groups: z.array(populatedEnvGroup).default([]),
|
|
|
})
|
|
|
.parseAsync(res.data);
|
|
|
|
|
|
- return environment_groups;
|
|
|
+ return environmentGroups;
|
|
|
}
|
|
|
);
|
|
|
|
|
|
@@ -159,7 +156,7 @@ const CreateApp: React.FC<CreateAppProps> = ({ history }) => {
|
|
|
env: [],
|
|
|
efsStorage: {
|
|
|
enabled: false,
|
|
|
- }
|
|
|
+ },
|
|
|
},
|
|
|
source: {
|
|
|
git_repo_name: "",
|
|
|
@@ -207,7 +204,7 @@ const CreateApp: React.FC<CreateAppProps> = ({ history }) => {
|
|
|
});
|
|
|
const { currentClusterResources } = useClusterResources();
|
|
|
|
|
|
- const resetAllExceptName = () => {
|
|
|
+ const resetAllExceptName = (): void => {
|
|
|
setIsNameHighlight(true);
|
|
|
|
|
|
// Get the current name value before the reset
|
|
|
@@ -262,7 +259,10 @@ const CreateApp: React.FC<CreateAppProps> = ({ history }) => {
|
|
|
}) => {
|
|
|
setIsDeploying(true);
|
|
|
// log analytics event that we started form submission
|
|
|
- updateAppStep({ step: "stack-launch-complete", appName: name.value });
|
|
|
+ void 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,7 +302,10 @@ const CreateApp: React.FC<CreateAppProps> = ({ history }) => {
|
|
|
);
|
|
|
|
|
|
// log analytics event that we successfully deployed
|
|
|
- updateAppStep({ step: "stack-launch-success", appName: name.value });
|
|
|
+ void updateAppStep({
|
|
|
+ step: "stack-launch-success",
|
|
|
+ appName: name.value,
|
|
|
+ });
|
|
|
|
|
|
if (source.type === "docker-registry") {
|
|
|
history.push(`/apps/${app.name}`);
|
|
|
@@ -315,7 +318,7 @@ const CreateApp: React.FC<CreateAppProps> = ({ history }) => {
|
|
|
});
|
|
|
|
|
|
if (axios.isAxiosError(err) && err.response?.data?.error) {
|
|
|
- updateAppStep({
|
|
|
+ void updateAppStep({
|
|
|
step: "stack-launch-failure",
|
|
|
errorMessage: err.response?.data?.error,
|
|
|
appName: name.value,
|
|
|
@@ -326,7 +329,7 @@ const CreateApp: React.FC<CreateAppProps> = ({ history }) => {
|
|
|
|
|
|
const msg =
|
|
|
"An error occurred while deploying your application. Please try again.";
|
|
|
- updateAppStep({
|
|
|
+ void updateAppStep({
|
|
|
step: "stack-launch-failure",
|
|
|
errorMessage: msg,
|
|
|
appName: name.value,
|
|
|
@@ -337,7 +340,7 @@ const CreateApp: React.FC<CreateAppProps> = ({ history }) => {
|
|
|
setIsDeploying(false);
|
|
|
}
|
|
|
},
|
|
|
- [currentProject?.id, currentCluster?.id, deploymentTarget]
|
|
|
+ [currentProject?.id, currentCluster?.id, deploymentTarget, name.value]
|
|
|
);
|
|
|
|
|
|
useEffect(() => {
|
|
|
@@ -358,7 +361,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 && image.tag) {
|
|
|
+ if (image?.tag) {
|
|
|
setStep((prev) => Math.max(prev, 2));
|
|
|
}
|
|
|
}
|
|
|
@@ -412,7 +415,7 @@ const CreateApp: React.FC<CreateAppProps> = ({ history }) => {
|
|
|
message: "I am running into an issue launching an application.",
|
|
|
});
|
|
|
|
|
|
- updateAppStep({
|
|
|
+ void updateAppStep({
|
|
|
step: "stack-launch-failure",
|
|
|
errorMessage: `Form validation error: ${errorMessage}`,
|
|
|
appName: name.value,
|
|
|
@@ -420,8 +423,6 @@ const CreateApp: React.FC<CreateAppProps> = ({ history }) => {
|
|
|
|
|
|
return <Error message={errorMessage} maxWidth="600px" />;
|
|
|
}
|
|
|
-
|
|
|
- return;
|
|
|
}, [isValidating, isDeploying, deployError, errors]);
|
|
|
|
|
|
const submitDisabled = useMemo(() => {
|
|
|
@@ -512,7 +513,11 @@ const CreateApp: React.FC<CreateAppProps> = ({ history }) => {
|
|
|
/>
|
|
|
<DarkMatter />
|
|
|
<FormProvider {...porterAppFormMethods}>
|
|
|
- <form onSubmit={onSubmit}>
|
|
|
+ <form
|
|
|
+ onSubmit={() => {
|
|
|
+ void onSubmit();
|
|
|
+ }}
|
|
|
+ >
|
|
|
<VerticalSteps
|
|
|
currentStep={step}
|
|
|
steps={[
|
|
|
@@ -520,7 +525,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
|
|
|
@@ -580,11 +585,11 @@ const CreateApp: React.FC<CreateAppProps> = ({ history }) => {
|
|
|
control={control}
|
|
|
render={({ field: { onChange, value } }) => (
|
|
|
<PorterYamlModal
|
|
|
- close={() =>
|
|
|
+ close={() => {
|
|
|
setUserHasSeenNoPorterYamlFoundModal(
|
|
|
true
|
|
|
- )
|
|
|
- }
|
|
|
+ );
|
|
|
+ }}
|
|
|
setPorterYamlPath={(porterYamlPath) => {
|
|
|
onChange(porterYamlPath);
|
|
|
}}
|
|
|
@@ -607,23 +612,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}
|
|
|
@@ -649,8 +654,9 @@ 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>
|
|
|
@@ -696,15 +702,17 @@ 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>
|
|
|
@@ -714,7 +722,9 @@ 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]}
|
|
|
@@ -722,8 +732,8 @@ const CreateApp: React.FC<CreateAppProps> = ({ history }) => {
|
|
|
stackName={name.value}
|
|
|
projectId={currentProject.id}
|
|
|
clusterId={currentCluster.id}
|
|
|
- deployPorterApp={() =>
|
|
|
- createAndApply({
|
|
|
+ deployPorterApp={async () =>
|
|
|
+ await createAndApply({
|
|
|
app: validatedAppProto,
|
|
|
source,
|
|
|
variables,
|
|
|
@@ -783,7 +793,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 {
|