Quellcode durchsuchen

Fix gpu enablement to check the contract instead of the cluster nodes, since gpu nodes start at 0 by default (#4300)

Feroze Mohideen vor 2 Jahren
Ursprung
Commit
3c344d6801

+ 3 - 1
dashboard/src/lib/hooks/useCluster.ts

@@ -451,8 +451,10 @@ type TUseClusterNodeList = {
 };
 export const useClusterNodeList = ({
   clusterId,
+  refetchInterval = 3000,
 }: {
   clusterId: number | undefined;
+  refetchInterval?: number;
 }): TUseClusterNodeList => {
   const { currentProject } = useContext(Context);
 
@@ -498,7 +500,7 @@ export const useClusterNodeList = ({
         .filter(valueExists);
     },
     {
-      refetchInterval: 3000,
+      refetchInterval,
       enabled:
         !!currentProject &&
         currentProject.id !== -1 &&

+ 6 - 0
dashboard/src/main/home/app-dashboard/app-view/tabs/Overview.tsx

@@ -4,6 +4,7 @@ import { useFormContext } from "react-hook-form";
 import Spacer from "components/porter/Spacer";
 import Text from "components/porter/Text";
 import { useAppStatus } from "lib/hooks/useAppStatus";
+import { useCluster } from "lib/hooks/useCluster";
 import { type PorterAppFormData } from "lib/porter-apps";
 import {
   defaultSerialized,
@@ -37,6 +38,10 @@ const Overview: React.FC<Props> = ({ buttonStatus }) => {
     latestClientServices,
   } = useLatestRevision();
 
+  const { cluster } = useCluster({
+    clusterId,
+  });
+
   const { serviceVersionStatus } = useAppStatus({
     projectId,
     clusterId,
@@ -77,6 +82,7 @@ const Overview: React.FC<Props> = ({ buttonStatus }) => {
           namespace: deploymentTarget.namespace,
           appName: porterApp.name,
         }}
+        cluster={cluster}
       />
       <Spacer y={0.75} />
       <AppSaveButton

+ 4 - 0
dashboard/src/main/home/app-dashboard/validate-apply/services-settings/ServiceContainer.tsx

@@ -6,6 +6,7 @@ import styled, { keyframes } from "styled-components";
 import { match } from "ts-pattern";
 
 import Spacer from "components/porter/Spacer";
+import { type ClientCluster } from "lib/clusters/types";
 import { type ClientServiceStatus } from "lib/hooks/useAppStatus";
 import { type PorterAppFormData } from "lib/porter-apps";
 import {
@@ -44,6 +45,7 @@ type ServiceProps = {
   clusterIngressIp: string;
   showDisableTls: boolean;
   existingServiceNames: string[];
+  cluster?: ClientCluster;
 };
 
 const ServiceContainer: React.FC<ServiceProps> = ({
@@ -60,6 +62,7 @@ const ServiceContainer: React.FC<ServiceProps> = ({
   clusterIngressIp,
   showDisableTls,
   existingServiceNames,
+  cluster,
 }) => {
   const renderTabs = (service: ClientService): JSX.Element => {
     return match(service)
@@ -74,6 +77,7 @@ const ServiceContainer: React.FC<ServiceProps> = ({
           internalNetworkingDetails={internalNetworkingDetails}
           clusterIngressIp={clusterIngressIp}
           showDisableTls={showDisableTls}
+          cluster={cluster}
         />
       ))
       .with({ config: { type: "worker" } }, (svc) => (

+ 4 - 0
dashboard/src/main/home/app-dashboard/validate-apply/services-settings/ServiceList.tsx

@@ -14,6 +14,7 @@ import { ControlledInput } from "components/porter/ControlledInput";
 import Modal from "components/porter/Modal";
 import Spacer from "components/porter/Spacer";
 import Text from "components/porter/Text";
+import { type ClientCluster } from "lib/clusters/types";
 import { type ClientServiceStatus } from "lib/hooks/useAppStatus";
 import { type PorterAppFormData } from "lib/porter-apps";
 import {
@@ -55,6 +56,7 @@ type ServiceListProps = {
     appName: string;
   };
   allowAddServices?: boolean;
+  cluster?: ClientCluster;
 };
 
 const ServiceList: React.FC<ServiceListProps> = ({
@@ -69,6 +71,7 @@ const ServiceList: React.FC<ServiceListProps> = ({
     appName: "",
   },
   allowAddServices = true,
+  cluster,
 }) => {
   // top level app form
   const { control: appControl } = useFormContext<PorterAppFormData>();
@@ -239,6 +242,7 @@ const ServiceList: React.FC<ServiceListProps> = ({
                 clusterIngressIp={clusterIngressIp}
                 showDisableTls={loadBalancerType === "ALB"}
                 existingServiceNames={existingServiceNames}
+                cluster={cluster}
               />
             ) : null;
           })}

+ 175 - 0
dashboard/src/main/home/app-dashboard/validate-apply/services-settings/tabs/GPUResources.tsx

@@ -0,0 +1,175 @@
+import React, { useMemo, useState } from "react";
+import { Switch } from "@material-ui/core";
+import { Controller, useFormContext } from "react-hook-form";
+import styled from "styled-components";
+
+import Loading from "components/Loading";
+import Container from "components/porter/Container";
+import Link from "components/porter/Link";
+import Modal from "components/porter/Modal";
+import Spacer from "components/porter/Spacer";
+import Tag from "components/porter/Tag";
+import Text from "components/porter/Text";
+import { type ClientCluster } from "lib/clusters/types";
+import { type PorterAppFormData } from "lib/porter-apps";
+
+import infra from "assets/cluster.svg";
+
+type Props = {
+  maxGPU: number;
+  index: number;
+  cluster: ClientCluster;
+};
+
+// TODO: allow users to provision multiple GPU nodes in the slider
+const GPUResources: React.FC<Props> = ({ index, cluster }) => {
+  const [clusterModalVisible, setClusterModalVisible] =
+    useState<boolean>(false);
+
+  const { control } = useFormContext<PorterAppFormData>();
+
+  const canEnableGPU = useMemo(() => {
+    return cluster?.contract.config.cluster.config.nodeGroups.some(
+      (ng) => ng.nodeGroupType === "CUSTOM"
+    );
+  }, [cluster]);
+  return (
+    <>
+      <Spacer y={1} />
+      <Controller
+        name={`app.services.${index}.gpu`}
+        control={control}
+        render={({ field: { value, onChange } }) => (
+          <>
+            <Container row>
+              <Switch
+                size="small"
+                color="primary"
+                checked={value.enabled.value}
+                onChange={() => {
+                  if (!canEnableGPU) {
+                    setClusterModalVisible(true);
+                    return;
+                  }
+                  onChange({
+                    ...value,
+                    enabled: {
+                      ...value.enabled,
+                      value: !value.enabled.value,
+                    },
+                    gpuCoresNvidia: {
+                      ...value.gpuCoresNvidia,
+                      value: value.enabled.value ? 0 : 1,
+                    },
+                  });
+                }}
+                inputProps={{ "aria-label": "controlled" }}
+              />
+              <Spacer inline x={0.5} />
+              <Text>
+                <>
+                  <span>Enable GPU</span>
+                </>
+              </Text>
+            </Container>
+
+            <Spacer y={0.5} />
+            {clusterModalVisible && (
+              <Modal
+                closeModal={() => {
+                  setClusterModalVisible(false);
+                }}
+              >
+                <div>
+                  <Text size={16}>Cluster GPU check</Text>
+                  <Spacer height="15px" />
+                  <Text color="helper">
+                    Your cluster is not yet configured to allow applications to
+                    run on GPU nodes.
+                  </Text>
+                  <Spacer height="15px" />
+                  <Link to={`/infrastructure/${cluster.id}`}>
+                    You can add a GPU node group to your cluster here.
+                  </Link>
+                </div>
+              </Modal>
+            )}
+          </>
+        )}
+      />
+      {/* {maxGPU > 1 && gpu.value && (
+        <>
+          <Spacer y={1} />
+          <Controller
+            name={`app.services.${index}.gpu`}
+            control={control}
+            render={({ field: { value, onChange } }) => (
+              <InputSlider
+                label="GPU"
+                unit=""
+                min={0}
+                max={maxGPU}
+                value={(value?.gpuCoresNvidia.value ?? 1).toString()}
+                disabled={value?.gpuCoresNvidia.readOnly}
+                setValue={(e) => {
+                  onChange({
+                    ...value,
+                    gpuCoresNvidia: {
+                      ...value.gpuCoresNvidia,
+                      value: e,
+                    },
+                  });
+                }}
+                disabledTooltip={
+                  "You may only edit this field in your porter.yaml."
+                }
+              />
+            )}
+          />
+        </>
+      )} */}
+      {cluster.contract.condition === "" && !canEnableGPU && (
+        <CheckItemContainer>
+          <CheckItemTop>
+            <Loading offset="0px" width="20px" height="20px" />
+            <Spacer inline x={1} />
+            <Text>{"Cluster is updating..."}</Text>
+            <Spacer inline x={1} />
+            <Tag>
+              <Link to={`/infrastructure/${cluster.id}`}>
+                <TagIcon src={infra} />
+                View Status
+              </Link>
+            </Tag>
+          </CheckItemTop>
+        </CheckItemContainer>
+      )}
+    </>
+  );
+};
+
+export default GPUResources;
+
+const CheckItemContainer = styled.div`
+  display: flex;
+  flex-direction: column;
+  border: 1px solid ${(props) => props.theme.border};
+  border-radius: 5px;
+  font-size: 13px;
+  width: 100%;
+  margin-bottom: 10px;
+  padding-left: 10px;
+  background: ${(props) => props.theme.clickable.bg};
+`;
+
+const CheckItemTop = styled.div`
+  display: flex;
+  align-items: center;
+  padding: 10px;
+  background: ${(props) => props.theme.clickable.bg};
+`;
+
+const TagIcon = styled.img`
+  height: 12px;
+  margin-right: 3px;
+`;

+ 189 - 0
dashboard/src/main/home/app-dashboard/validate-apply/services-settings/tabs/Old_GPUResources.tsx

@@ -0,0 +1,189 @@
+import React, { useContext, useState } from "react";
+import { Switch } from "@material-ui/core";
+import { Controller, useFormContext } from "react-hook-form";
+import styled from "styled-components";
+
+import Loading from "components/Loading";
+import Container from "components/porter/Container";
+import InputSlider from "components/porter/InputSlider";
+import Link from "components/porter/Link";
+import Spacer from "components/porter/Spacer";
+import Tag from "components/porter/Tag";
+import Text from "components/porter/Text";
+import ProvisionClusterModal from "main/home/sidebar/ProvisionClusterModal";
+import { type PorterAppFormData } from "lib/porter-apps";
+
+import { Context } from "shared/Context";
+import addCircle from "assets/add-circle.png";
+import infra from "assets/cluster.svg";
+
+type Props = {
+  clusterContainsGPUNodes: boolean;
+  maxGPU: number;
+  index: number;
+};
+
+// TODO: delete this file and all references once new infra tab is GA
+const OldGPUResources: React.FC<Props> = ({
+  clusterContainsGPUNodes,
+  maxGPU,
+  index,
+}) => {
+  const { currentCluster } = useContext(Context);
+  const [clusterModalVisible, setClusterModalVisible] =
+    useState<boolean>(false);
+
+  const { control, watch } = useFormContext<PorterAppFormData>();
+  const gpu = watch(`app.services.${index}.gpu.enabled`, {
+    readOnly: false,
+    value: false,
+  });
+  return (
+    <>
+      <Spacer y={1} />
+      <Controller
+        name={`app.services.${index}.gpu`}
+        control={control}
+        render={({ field: { value, onChange } }) => (
+          <>
+            <Container row>
+              <Switch
+                size="small"
+                color="primary"
+                checked={value.enabled.value}
+                disabled={!clusterContainsGPUNodes}
+                onChange={() => {
+                  onChange({
+                    ...value,
+                    enabled: {
+                      ...value.enabled,
+                      value: !value.enabled.value,
+                    },
+                    gpuCoresNvidia: {
+                      ...value.gpuCoresNvidia,
+                      value: value.enabled.value ? 0 : 1,
+                    },
+                  });
+                }}
+                inputProps={{ "aria-label": "controlled" }}
+              />
+              <Spacer inline x={0.5} />
+              <Text>
+                <>
+                  <span>Enable GPU</span>
+                </>
+              </Text>
+
+              {!clusterContainsGPUNodes && (
+                <>
+                  <Spacer inline x={1} />
+                  <Text color="helper">
+                    Your cluster has no GPU nodes available.
+                  </Text>
+                  <Spacer inline x={0.5} />
+                  {currentCluster?.status !== "UPDATING" && (
+                    <Tag>
+                      <Link
+                        onClick={() => {
+                          setClusterModalVisible(true);
+                        }}
+                      >
+                        <TagIcon src={addCircle} />
+                        Add GPU nodes
+                      </Link>
+                    </Tag>
+                  )}
+                </>
+              )}
+            </Container>
+
+            <Spacer y={0.5} />
+            {clusterModalVisible && (
+              <ProvisionClusterModal
+                closeModal={() => {
+                  setClusterModalVisible(false);
+                }}
+                gpuModal={true}
+                gcp={currentCluster?.cloud_provider === "GCP"}
+                azure={currentCluster?.cloud_provider === "Azure"}
+              />
+            )}
+          </>
+        )}
+      />
+      {maxGPU > 1 && gpu.value && (
+        <>
+          <Spacer y={1} />
+          <Controller
+            name={`app.services.${index}.gpu`}
+            control={control}
+            render={({ field: { value, onChange } }) => (
+              <InputSlider
+                label="GPU"
+                unit=""
+                min={0}
+                max={maxGPU}
+                value={(value?.gpuCoresNvidia.value ?? 1).toString()}
+                disabled={value?.gpuCoresNvidia.readOnly}
+                setValue={(e) => {
+                  onChange({
+                    ...value,
+                    gpuCoresNvidia: {
+                      ...value.gpuCoresNvidia,
+                      value: e,
+                    },
+                  });
+                }}
+                disabledTooltip={
+                  "You may only edit this field in your porter.yaml."
+                }
+              />
+            )}
+          />
+        </>
+      )}
+      {currentCluster?.status === "UPDATING" && !clusterContainsGPUNodes && (
+        <CheckItemContainer>
+          <CheckItemTop>
+            <Loading offset="0px" width="20px" height="20px" />
+            <Spacer inline x={1} />
+            <Text>{"Cluster is updating..."}</Text>
+            <Spacer inline x={1} />
+            <Tag>
+              <Link to={`/cluster-dashboard`}>
+                <TagIcon src={infra} />
+                View Status
+              </Link>
+            </Tag>
+          </CheckItemTop>
+        </CheckItemContainer>
+      )}
+    </>
+  );
+};
+
+export default OldGPUResources;
+
+const CheckItemContainer = styled.div`
+  display: flex;
+  flex-direction: column;
+  border: 1px solid ${(props) => props.theme.border};
+  border-radius: 5px;
+  font-size: 13px;
+  width: 100%;
+  margin-bottom: 10px;
+  padding-left: 10px;
+  background: ${(props) => props.theme.clickable.bg};
+`;
+
+const CheckItemTop = styled.div`
+  display: flex;
+  align-items: center;
+  padding: 10px;
+  background: ${(props) => props.theme.clickable.bg};
+`;
+
+const TagIcon = styled.img`
+  height: 12px;
+  margin-right: 3px;
+`;

+ 21 - 246
dashboard/src/main/home/app-dashboard/validate-apply/services-settings/tabs/Resources.tsx

@@ -1,32 +1,23 @@
 import React, { useContext, useState } from "react";
-import { Switch } from "@material-ui/core";
 import { Controller, useFormContext } from "react-hook-form";
-import styled from "styled-components";
 import { match } from "ts-pattern";
 
-import Loading from "components/Loading";
 import Checkbox from "components/porter/Checkbox";
-import Container from "components/porter/Container";
 import { ControlledInput } from "components/porter/ControlledInput";
 import InputSlider from "components/porter/InputSlider";
-import Link from "components/porter/Link";
 import Spacer from "components/porter/Spacer";
-import Tag from "components/porter/Tag";
 import Text from "components/porter/Text";
 import SmartOptModal from "main/home/app-dashboard/new-app-flow/tabs/SmartOptModal";
-import ProvisionClusterModal from "main/home/sidebar/ProvisionClusterModal";
-import {
-  closestMultiplier,
-  lowestClosestResourceMultipler,
-} from "lib/hooks/useClusterResourceLimits";
+import { type ClientCluster } from "lib/clusters/types";
+import { closestMultiplier } from "lib/hooks/useClusterResourceLimits";
 import { type PorterAppFormData } from "lib/porter-apps";
 import { type ClientService } from "lib/porter-apps/services";
 
 import { Context } from "shared/Context";
-import addCircle from "assets/add-circle.png";
-import infra from "assets/cluster.svg";
 
+import GPUResources from "./GPUResources";
 import IntelligentSlider from "./IntelligentSlider";
+import OldGPUResources from "./Old_GPUResources";
 
 type ResourcesProps = {
   index: number;
@@ -36,6 +27,7 @@ type ResourcesProps = {
   isPredeploy?: boolean;
   clusterContainsGPUNodes: boolean;
   maxGPU: number;
+  cluster?: ClientCluster;
 };
 
 const Resources: React.FC<ResourcesProps> = ({
@@ -46,13 +38,13 @@ const Resources: React.FC<ResourcesProps> = ({
   service,
   clusterContainsGPUNodes,
   isPredeploy = false,
+  cluster,
 }) => {
   const { control, register, watch, setValue } =
     useFormContext<PorterAppFormData>();
   const [showNeedHelpModal, setShowNeedHelpModal] = useState(false);
-  const [clusterModalVisible, setClusterModalVisible] =
-    useState<boolean>(false);
-  const { currentCluster, currentProject } = useContext(Context);
+
+  const { currentProject } = useContext(Context);
 
   const autoscalingEnabled = watch(
     `app.services.${index}.config.autoscaling.enabled`,
@@ -67,77 +59,9 @@ const Resources: React.FC<ResourcesProps> = ({
     value: false,
   });
 
-  const memory = watch(`app.services.${index}.ramMegabytes`, {
-    readOnly: false,
-    value: 0,
-  });
-  const cpu = watch(`app.services.${index}.cpuCores`, {
-    readOnly: false,
-    value: 0,
-  });
-  const gpu = watch(`app.services.${index}.gpu.enabled`, {
-    readOnly: false,
-    value: false,
-  });
-
   return (
     <>
       <Spacer y={1} />
-      <Controller
-        name={
-          isPredeploy
-            ? `app.predeploy.${index}.smartOptimization`
-            : `app.services.${index}.smartOptimization`
-        }
-        control={control}
-        render={({ field: { value, onChange } }) => {!currentProject?.sandbox_enabled && (
-          <SmartOptHeader>
-            <StyledIcon
-              className="material-icons"
-              onClick={() => {
-                setShowNeedHelpModal(true);
-              }}
-            >
-              help_outline
-            </StyledIcon>
-            <Text>Smart Optimization</Text>
-            <Switch
-              size="small"
-              color="primary"
-              disabled={false}
-              checked={value?.value}
-              onChange={() => {
-                if (!value?.value) {
-                  const lowestRAM = lowestClosestResourceMultipler(
-                    0,
-                    maxRAM,
-                    memory.value
-                  );
-                  const lowestCPU = lowestClosestResourceMultipler(
-                    0,
-                    maxCPU,
-                    cpu.value
-                  );
-                  const lowestFraction = Math.min(lowestRAM, lowestCPU);
-                  setValue(`app.services.${index}.cpuCores`, {
-                    readOnly: false,
-                    value: Number((maxCPU * lowestFraction).toFixed(2)),
-                  });
-                  setValue(`app.services.${index}.ramMegabytes`, {
-                    readOnly: false,
-                    value: maxRAM * lowestFraction,
-                  });
-                }
-                onChange({
-                  ...value,
-                  value: !value?.value,
-                });
-              }}
-              inputProps={{ "aria-label": "controlled" }}
-            />
-          </SmartOptHeader>
-        )}}
-      />
       {showNeedHelpModal && (
         <SmartOptModal setModalVisible={setShowNeedHelpModal} />
       )}
@@ -177,7 +101,7 @@ const Resources: React.FC<ResourcesProps> = ({
             disabledTooltip={
               "You may only edit this field in your porter.yaml."
             }
-            isSmartOptimizationOn={!currentProject?.sandbox_enabled && (smartOpt?.value ?? false)}
+            isSmartOptimizationOn={false}
             decimalsToRoundTo={2}
           />
         )}
@@ -219,133 +143,23 @@ const Resources: React.FC<ResourcesProps> = ({
             disabledTooltip={
               "You may only edit this field in your porter.yaml."
             }
-            isSmartOptimizationOn={!currentProject?.sandbox_enabled && (smartOpt?.value ?? false)}
+            isSmartOptimizationOn={false}
           />
         )}
       />
 
-      {currentProject?.gpu_enabled && (
-        <>
-          <Spacer y={1} />
-          <Controller
-            name={`app.services.${index}.gpu`}
-            control={control}
-            render={({ field: { value, onChange } }) => (
-              <>
-                <Container row>
-                  <Switch
-                    size="small"
-                    color="primary"
-                    checked={value.enabled.value}
-                    disabled={!clusterContainsGPUNodes}
-                    onChange={() => {
-                      onChange({
-                        ...value,
-                        enabled: {
-                          ...value.enabled,
-                          value: !value.enabled.value,
-                        },
-                        gpuCoresNvidia: {
-                          ...value.gpuCoresNvidia,
-                          value: value.enabled.value ? 0 : 1,
-                        },
-                      });
-                    }}
-                    inputProps={{ "aria-label": "controlled" }}
-                  />
-                  <Spacer inline x={0.5} />
-                  <Text>
-                    <>
-                      <span>Enable GPU</span>
-                    </>
-                  </Text>
-
-                  {!clusterContainsGPUNodes && (
-                    <>
-                      <Spacer inline x={1} />
-                      <Text color="helper">
-                        Your cluster has no GPU nodes available.
-                      </Text>
-                      <Spacer inline x={0.5} />
-                      {currentCluster.status !== "UPDATING" && (
-                        <Tag>
-                          <Link
-                            onClick={() => {
-                              setClusterModalVisible(true);
-                            }}
-                          >
-                            <TagIcon src={addCircle} />
-                            Add GPU nodes
-                          </Link>
-                        </Tag>
-                      )}
-                    </>
-                  )}
-                </Container>
-
-                <Spacer y={0.5} />
-                {clusterModalVisible && (
-                  <ProvisionClusterModal
-                    closeModal={() => {
-                      setClusterModalVisible(false);
-                    }}
-                    gpuModal={true}
-                    gcp={currentCluster?.cloud_provider === "GCP"}
-                    azure={currentCluster?.cloud_provider === "Azure"}
-                  />
-                )}
-              </>
-            )}
+      {currentProject?.gpu_enabled &&
+        (currentProject?.beta_features_enabled ? (
+          cluster && (
+            <GPUResources index={index} maxGPU={maxGPU} cluster={cluster} />
+          )
+        ) : (
+          <OldGPUResources
+            clusterContainsGPUNodes={clusterContainsGPUNodes}
+            maxGPU={maxGPU}
+            index={index}
           />
-          {maxGPU > 1 && gpu.value && (
-            <>
-              <Spacer y={1} />
-              <Controller
-                name={`app.services.${index}.gpu`}
-                control={control}
-                render={({ field: { value, onChange } }) => (
-                  <InputSlider
-                    label="GPU"
-                    unit=""
-                    min={0}
-                    max={maxGPU}
-                    value={value?.gpuCoresNvidia.value ?? "1"}
-                    disabled={value?.readOnly}
-                    setValue={(e) => {
-                      onChange({
-                        ...value,
-                        gpuCoresNvidia: {
-                          ...value.gpuCoresNvidia,
-                          value: e,
-                        },
-                      });
-                    }}
-                    disabledTooltip={
-                      "You may only edit this field in your porter.yaml."
-                    }
-                  />
-                )}
-              />
-            </>
-          )}
-          {currentCluster.status === "UPDATING" && !clusterContainsGPUNodes && (
-            <CheckItemContainer>
-              <CheckItemTop>
-                <Loading offset="0px" width="20px" height="20px" />
-                <Spacer inline x={1} />
-                <Text>{"Cluster is updating..."}</Text>
-                <Spacer inline x={1} />
-                <Tag>
-                  <Link to={`/cluster-dashboard`}>
-                    <TagIcon src={infra} />
-                    View Status
-                  </Link>
-                </Tag>
-              </CheckItemTop>
-            </CheckItemContainer>
-          )}
-        </>
-      )}
+        ))}
       {match(service.config)
         .with({ type: "job" }, () => null)
         .with({ type: "predeploy" }, () => null)
@@ -506,42 +320,3 @@ const Resources: React.FC<ResourcesProps> = ({
 };
 
 export default Resources;
-
-const StyledIcon = styled.i`
-  cursor: pointer;
-  font-size: 16px;
-  margin-right: 5px;
-  &:hover {
-    color: #666;
-  }
-`;
-
-const SmartOptHeader = styled.div`
-  display: flex;
-  align-items: center;
-  justify-content: flex-end;
-`;
-
-const CheckItemContainer = styled.div`
-  display: flex;
-  flex-direction: column;
-  border: 1px solid ${(props) => props.theme.border};
-  border-radius: 5px;
-  font-size: 13px;
-  width: 100%;
-  margin-bottom: 10px;
-  padding-left: 10px;
-  background: ${(props) => props.theme.clickable.bg};
-`;
-
-const CheckItemTop = styled.div`
-  display: flex;
-  align-items: center;
-  padding: 10px;
-  background: ${(props) => props.theme.clickable.bg};
-`;
-
-const TagIcon = styled.img`
-  height: 12px;
-  margin-right: 3px;
-`;

+ 4 - 0
dashboard/src/main/home/app-dashboard/validate-apply/services-settings/tabs/WebTabs.tsx

@@ -3,6 +3,7 @@ import { match } from "ts-pattern";
 
 import Spacer from "components/porter/Spacer";
 import TabSelector from "components/TabSelector";
+import { type ClientCluster } from "lib/clusters/types";
 import { type ClientService } from "lib/porter-apps/services";
 
 import Advanced from "./Advanced";
@@ -28,6 +29,7 @@ type Props = {
   };
   clusterIngressIp: string;
   showDisableTls: boolean;
+  cluster?: ClientCluster;
 };
 
 const WebTabs: React.FC<Props> = ({
@@ -40,6 +42,7 @@ const WebTabs: React.FC<Props> = ({
   internalNetworkingDetails,
   clusterIngressIp,
   showDisableTls,
+  cluster,
 }) => {
   const [currentTab, setCurrentTab] = React.useState<
     "main" | "resources" | "networking" | "advanced"
@@ -76,6 +79,7 @@ const WebTabs: React.FC<Props> = ({
             maxGPU={maxGPU}
             clusterContainsGPUNodes={clusterContainsGPUNodes}
             service={service}
+            cluster={cluster}
           />
         ))
         .with("advanced", () => (

+ 4 - 2
dashboard/src/main/home/infrastructure-dashboard/ClusterContextProvider.tsx

@@ -47,21 +47,23 @@ export const useClusterContext = (): ClusterContextType => {
 
 type ClusterContextProviderProps = {
   clusterId?: number;
+  refetchInterval?: number;
   children: JSX.Element;
 };
 
 const ClusterContextProvider: React.FC<ClusterContextProviderProps> = ({
   clusterId,
   children,
+  refetchInterval = 3000,
 }) => {
   const { currentProject } = useContext(Context);
   const { cluster, isLoading, isError } = useCluster({
     clusterId,
-    refetchInterval: 3000,
+    refetchInterval,
   });
   const { reportToAnalytics } = useClusterAnalytics();
 
-  const { nodes } = useClusterNodeList({ clusterId });
+  const { nodes } = useClusterNodeList({ clusterId, refetchInterval });
 
   const paramsExist =
     !!clusterId && !!currentProject && currentProject.id !== -1;