|
|
@@ -1,5 +1,6 @@
|
|
|
import React, { createContext, useMemo, useState } from "react";
|
|
|
import { zodResolver } from "@hookform/resolvers/zod";
|
|
|
+import { Contract } from "@porter-dev/api-contracts";
|
|
|
import { useQueryClient } from "@tanstack/react-query";
|
|
|
import { FormProvider, useForm } from "react-hook-form";
|
|
|
import { useHistory } from "react-router";
|
|
|
@@ -8,11 +9,20 @@ import styled from "styled-components";
|
|
|
import Loading from "components/Loading";
|
|
|
import { Error as ErrorComponent } from "components/porter/Error";
|
|
|
import { clientAddonValidator, type ClientAddon } from "lib/addons";
|
|
|
+import { updateExistingClusterContract } from "lib/clusters";
|
|
|
+import { type ClientPreflightCheck } from "lib/clusters/types";
|
|
|
import { useAddon } from "lib/hooks/useAddon";
|
|
|
-import { getErrorMessageFromNetworkCall } from "lib/hooks/useCluster";
|
|
|
+import {
|
|
|
+ getErrorMessageFromNetworkCall,
|
|
|
+ preflightChecks,
|
|
|
+} from "lib/hooks/useCluster";
|
|
|
import { useDefaultDeploymentTarget } from "lib/hooks/useDeploymentTarget";
|
|
|
|
|
|
-import { type UpdateClusterButtonProps } from "../infrastructure-dashboard/ClusterFormContextProvider";
|
|
|
+import { useClusterContext } from "../infrastructure-dashboard/ClusterContextProvider";
|
|
|
+import ClusterFormContextProvider, {
|
|
|
+ type UpdateClusterButtonProps,
|
|
|
+} from "../infrastructure-dashboard/ClusterFormContextProvider";
|
|
|
+import PreflightChecksModal from "../infrastructure-dashboard/modals/PreflightChecksModal";
|
|
|
|
|
|
type AddonFormContextType = {
|
|
|
updateAddonButtonProps: UpdateClusterButtonProps;
|
|
|
@@ -43,6 +53,12 @@ const AddonFormContextProvider: React.FC<AddonFormContextProviderProps> = ({
|
|
|
children,
|
|
|
}) => {
|
|
|
const [updateAddonError, setUpdateAddonError] = useState<string>("");
|
|
|
+ const [failingPreflightChecks, setFailingPreflightChecks] = useState<
|
|
|
+ ClientPreflightCheck[]
|
|
|
+ >([]);
|
|
|
+ const [isCheckingQuotas, setIsCheckingQuotas] = useState<boolean>(false);
|
|
|
+ const { cluster } = useClusterContext();
|
|
|
+
|
|
|
const { defaultDeploymentTarget } = useDefaultDeploymentTarget();
|
|
|
const { updateAddon } = useAddon();
|
|
|
const queryClient = useQueryClient();
|
|
|
@@ -57,12 +73,81 @@ const AddonFormContextProvider: React.FC<AddonFormContextProviderProps> = ({
|
|
|
formState: { isSubmitting, errors },
|
|
|
} = addonForm;
|
|
|
|
|
|
+ const modelAddonPreflightChecks = async (
|
|
|
+ addon: ClientAddon,
|
|
|
+ projectId: number
|
|
|
+ ): Promise<ClientPreflightCheck[] | undefined> => {
|
|
|
+ // TODO: figure out why data.template is undefined here. If it is defined, we can use data.template.isModelTemplate instead of hardcoding the type
|
|
|
+ if (
|
|
|
+ addon.config.type === "deepgram" &&
|
|
|
+ cluster.contract?.config &&
|
|
|
+ cluster.contract.config.cluster.cloudProvider === "AWS"
|
|
|
+ ) {
|
|
|
+ let clientContract = cluster.contract.config;
|
|
|
+ if (
|
|
|
+ !clientContract.cluster.config.nodeGroups.some(
|
|
|
+ (n) => n.nodeGroupType === "CUSTOM"
|
|
|
+ )
|
|
|
+ ) {
|
|
|
+ clientContract = {
|
|
|
+ ...clientContract,
|
|
|
+ cluster: {
|
|
|
+ ...clientContract.cluster,
|
|
|
+ config: {
|
|
|
+ ...clientContract.cluster.config,
|
|
|
+ nodeGroups: [
|
|
|
+ ...clientContract.cluster.config.nodeGroups,
|
|
|
+ {
|
|
|
+ nodeGroupType: "CUSTOM",
|
|
|
+ instanceType: "g4dn.xlarge",
|
|
|
+ minInstances: 0,
|
|
|
+ maxInstances: 1,
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ },
|
|
|
+ };
|
|
|
+ }
|
|
|
+ const contract = Contract.fromJsonString(
|
|
|
+ atob(cluster.contract.base64_contract),
|
|
|
+ {
|
|
|
+ ignoreUnknownFields: true,
|
|
|
+ }
|
|
|
+ );
|
|
|
+ const contractCluster = contract.cluster;
|
|
|
+ if (contractCluster) {
|
|
|
+ const newContract = new Contract({
|
|
|
+ ...contract,
|
|
|
+ cluster: updateExistingClusterContract(
|
|
|
+ clientContract,
|
|
|
+ contractCluster
|
|
|
+ ),
|
|
|
+ });
|
|
|
+ setIsCheckingQuotas(true);
|
|
|
+ const preflightCheckResults = await preflightChecks(
|
|
|
+ newContract,
|
|
|
+ projectId
|
|
|
+ );
|
|
|
+ return preflightCheckResults;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
const onSubmit = handleSubmit(async (data) => {
|
|
|
if (!projectId) {
|
|
|
return;
|
|
|
}
|
|
|
+ setFailingPreflightChecks([]);
|
|
|
setUpdateAddonError("");
|
|
|
try {
|
|
|
+ const preflightCheckResults = await modelAddonPreflightChecks(
|
|
|
+ data,
|
|
|
+ projectId
|
|
|
+ );
|
|
|
+ if (preflightCheckResults) {
|
|
|
+ setFailingPreflightChecks(preflightCheckResults);
|
|
|
+ }
|
|
|
+ setIsCheckingQuotas(false);
|
|
|
await updateAddon({
|
|
|
projectId,
|
|
|
deploymentTargetId: defaultDeploymentTarget.id,
|
|
|
@@ -91,6 +176,9 @@ const AddonFormContextProvider: React.FC<AddonFormContextProviderProps> = ({
|
|
|
props.status = "loading";
|
|
|
props.isDisabled = true;
|
|
|
}
|
|
|
+ if (isCheckingQuotas) {
|
|
|
+ props.loadingText = "Checking quotas...";
|
|
|
+ }
|
|
|
|
|
|
if (updateAddonError) {
|
|
|
props.status = (
|
|
|
@@ -103,7 +191,7 @@ const AddonFormContextProvider: React.FC<AddonFormContextProviderProps> = ({
|
|
|
}
|
|
|
|
|
|
return props;
|
|
|
- }, [isSubmitting, errors, errors?.name?.value]);
|
|
|
+ }, [isSubmitting, errors, errors?.name?.value, isCheckingQuotas]);
|
|
|
|
|
|
if (!projectId) {
|
|
|
return <Loading />;
|
|
|
@@ -120,6 +208,16 @@ const AddonFormContextProvider: React.FC<AddonFormContextProviderProps> = ({
|
|
|
<FormProvider {...addonForm}>
|
|
|
<form onSubmit={onSubmit}>{children}</form>
|
|
|
</FormProvider>
|
|
|
+ {failingPreflightChecks.length > 0 && (
|
|
|
+ <ClusterFormContextProvider projectId={projectId}>
|
|
|
+ <PreflightChecksModal
|
|
|
+ onClose={() => {
|
|
|
+ setFailingPreflightChecks([]);
|
|
|
+ }}
|
|
|
+ preflightChecks={failingPreflightChecks}
|
|
|
+ />
|
|
|
+ </ClusterFormContextProvider>
|
|
|
+ )}
|
|
|
</Wrapper>
|
|
|
</AddonFormContext.Provider>
|
|
|
);
|