diff --git a/dashboard/src/main/home/app-dashboard/create-app/CreateApp.tsx b/dashboard/src/main/home/app-dashboard/create-app/CreateApp.tsx index 9691e0582b..2ddb8338af 100644 --- a/dashboard/src/main/home/app-dashboard/create-app/CreateApp.tsx +++ b/dashboard/src/main/home/app-dashboard/create-app/CreateApp.tsx @@ -220,8 +220,10 @@ const CreateApp: React.FC = ({ history }) => { const onSubmit = handleSubmit(async (data) => { try { setDeployError(""); - const { validatedAppProto, variables, secrets } = - await validateApp(data, currentProject?.beta_features_enabled); + const { validatedAppProto, variables, secrets } = await validateApp( + data, + currentProject?.beta_features_enabled + ); setValidatedAppProto(validatedAppProto); setFinalizedAppEnv({ variables, secrets }); @@ -596,7 +598,7 @@ const CreateApp: React.FC = ({ history }) => { Learn more diff --git a/dashboard/src/main/home/app-dashboard/validate-apply/services-settings/tabs/Health.tsx b/dashboard/src/main/home/app-dashboard/validate-apply/services-settings/tabs/Health.tsx index 82b4eb353a..1b77241f27 100644 --- a/dashboard/src/main/home/app-dashboard/validate-apply/services-settings/tabs/Health.tsx +++ b/dashboard/src/main/home/app-dashboard/validate-apply/services-settings/tabs/Health.tsx @@ -2,6 +2,7 @@ import React from "react"; import { Controller, useFormContext } from "react-hook-form"; import Checkbox from "components/porter/Checkbox"; +import Container from "components/porter/Container"; import { ControlledInput } from "components/porter/ControlledInput"; import Spacer from "components/porter/Spacer"; import Text from "components/porter/Text"; @@ -21,18 +22,20 @@ const Health: React.FC = ({ index }) => { return ( <> - - <> - Health checks + Health checks + + + + Configure health checks to prevent downtime during deployments  (?) - - + + = ({ index }) => { /> {healthCheckEnabled.value && ( <> + + Health check endpoint = ({ Custom domains @@ -154,7 +154,7 @@ const Networking: React.FC = ({ Ingress Custom Annotations diff --git a/dashboard/src/main/home/app-dashboard/validate-apply/services-settings/tabs/Resources.tsx b/dashboard/src/main/home/app-dashboard/validate-apply/services-settings/tabs/Resources.tsx index 67eab7cd36..72a7d37728 100644 --- a/dashboard/src/main/home/app-dashboard/validate-apply/services-settings/tabs/Resources.tsx +++ b/dashboard/src/main/home/app-dashboard/validate-apply/services-settings/tabs/Resources.tsx @@ -1,25 +1,33 @@ -import React, { useContext, useEffect, useState } from "react"; -import Spacer from "components/porter/Spacer"; -import { type ClientService } from "lib/porter-apps/services"; +import React, { useContext, useState } from "react"; +import { Switch } from "@material-ui/core"; import { Controller, useFormContext } from "react-hook-form"; -import { type PorterAppFormData } from "lib/porter-apps"; -import { ControlledInput } from "components/porter/ControlledInput"; +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 { match } from "ts-pattern"; -import styled from "styled-components"; -import { Switch } from "@material-ui/core"; import SmartOptModal from "main/home/app-dashboard/new-app-flow/tabs/SmartOptModal"; -import IntelligentSlider from "./IntelligentSlider"; -import InputSlider from "components/porter/InputSlider"; -import { closestMultiplier, lowestClosestResourceMultipler } from "lib/hooks/useClusterResourceLimits"; -import Loading from "components/Loading"; import ProvisionClusterModal from "main/home/sidebar/ProvisionClusterModal"; +import { + closestMultiplier, + lowestClosestResourceMultipler, +} 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 Link from "components/porter/Link"; -import Tag from "components/porter/Tag"; +import addCircle from "assets/add-circle.png"; import infra from "assets/cluster.svg"; +import IntelligentSlider from "./IntelligentSlider"; + type ResourcesProps = { index: number; maxCPU: number; @@ -37,50 +45,51 @@ const Resources: React.FC = ({ clusterContainsGPUNodes, isPredeploy = false, }) => { - const { control, register, watch, setValue } = useFormContext(); + const { control, register, watch, setValue } = + useFormContext(); const [showNeedHelpModal, setShowNeedHelpModal] = useState(false); - const [clusterModalVisible, setClusterModalVisible] = useState(false); + const [clusterModalVisible, setClusterModalVisible] = + useState(false); const { currentCluster, currentProject } = useContext(Context); const autoscalingEnabled = watch( - `app.services.${index}.config.autoscaling.enabled`, { - readOnly: false, - value: false - } + `app.services.${index}.config.autoscaling.enabled`, + { + readOnly: false, + value: false, + } ); - const smartOpt = watch( - `app.services.${index}.smartOptimization`, { + const smartOpt = watch(`app.services.${index}.smartOptimization`, { readOnly: false, - value: false - } - ); + value: false, + }); - const memory = watch( - `app.services.${index}.ramMegabytes`, { + const memory = watch(`app.services.${index}.ramMegabytes`, { readOnly: false, - value: 0 - } - ); - const cpu = watch( - `app.services.${index}.cpuCores`, { + value: 0, + }); + const cpu = watch(`app.services.${index}.cpuCores`, { readOnly: false, - value: 0 - } - ); + value: 0, + }); return ( <> ( { - setShowNeedHelpModal(true) + setShowNeedHelpModal(true); }} > help_outline @@ -89,36 +98,47 @@ const Resources: React.FC = ({ { - 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, + 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, }); } - } - inputProps={{ 'aria-label': 'controlled' }} + onChange({ + ...value, + value: !value?.value, + }); + }} + inputProps={{ "aria-label": "controlled" }} /> - )} /> - {showNeedHelpModal && - } + + )} + /> + {showNeedHelpModal && ( + + )} = ({ value={value.value.toString()} setValue={(e) => { if (smartOpt?.value) { - setValue( - `app.services.${index}.ramMegabytes`, { + setValue(`app.services.${index}.ramMegabytes`, { readOnly: false, - value: Number((closestMultiplier(0, maxCPU, value.value) * maxRAM).toFixed(0)) + value: Number( + ( + closestMultiplier(0, maxCPU, value.value) * maxRAM + ).toFixed(0) + ), }); } onChange({ @@ -172,13 +195,17 @@ const Resources: React.FC = ({ min={0} max={maxRAM} color={"#3f51b5"} - value={(value.value).toString()} + value={value.value.toString()} setValue={(e) => { if (smartOpt?.value) { setValue(`app.services.${index}.cpuCores`, { readOnly: false, - value: Number((closestMultiplier(0, maxRAM, value.value) * maxCPU).toFixed(2)) - }) + value: Number( + ( + closestMultiplier(0, maxRAM, value.value) * maxCPU + ).toFixed(2) + ), + }); } onChange({ ...value, @@ -195,246 +222,250 @@ const Resources: React.FC = ({ )} /> - {(currentCluster.cloud_provider === "AWS" && currentProject.gpu_enabled) && - <> - - ( - <> + {currentCluster?.cloud_provider === "AWS" && + currentProject?.gpu_enabled && ( + <> + + ( <> - 0} - disabled={!clusterContainsGPUNodes} - onChange={() => { - if (value.value > 0) { - onChange({ - ...value, - value: 0 - }); - } - else - onChange({ - ...value, - value: 1 - }); - }} - - inputProps={{ 'aria-label': 'controlled' }} /> - <> - Enable GPU - - - { - !clusterContainsGPUNodes && - <> - - - - You cluster has no GPU nodes available. - - - { setClusterModalVisible(true); }} - hasunderline - > - Add GPU nodes - - {/* -  (?) - */} - - - } - - - { - clusterModalVisible && { - setClusterModalVisible(false); - }} - gpuModal={true} - /> - } - - )} /> - {(currentCluster.status === "UPDATING" && clusterContainsGPUNodes) && - < CheckItemContainer > - - - - {"Creating GPU nodes..."} - - - - - View Status - - - - - } - - } - { - match(service.config) - .with({ type: "job" }, () => null) - .with({ type: "predeploy" }, () => null) - .otherwise((config) => ( - <> - - - - - {!clusterContainsGPUNodes && ( ( - { - onChange({ - ...value, - value: !value.value, - }); - }} - disabled={value.readOnly} - disabledTooltip={ - "You may only edit this field in your porter.yaml." - } - > - - Enable autoscaling (overrides instances) - - - )} - />)} - - - {autoscalingEnabled.value && ( - <> - - - - - - ( - { + + 0} + disabled={!clusterContainsGPUNodes} + onChange={() => { + if (value.value > 0) { onChange({ ...value, - value: e, + value: 0, }); - }} - disabledTooltip={ - value?.readOnly - ? "You may only edit this field in your porter.yaml." - : "Enable autoscaling to specify CPU threshold." - } - /> - )} - /> - - ( - { + } else onChange({ ...value, - value: e, + value: 1, }); - }} - disabledTooltip={ - value?.readOnly - ? "You may only edit this field in your porter.yaml." - : "Enable autoscaling to specify RAM threshold." - } - /> + }} + inputProps={{ "aria-label": "controlled" }} + /> + + + <> + Enable GPU + + + {!clusterContainsGPUNodes && ( + <> + + + You cluster has no GPU nodes available. + + + + { + setClusterModalVisible(true); + }} + > + + Add GPU nodes + + + )} - /> + + + + {clusterModalVisible && ( + { + setClusterModalVisible(false); + }} + gpuModal={true} + /> + )} )} - - )) - } + /> + {currentCluster.status === "UPDATING" && + clusterContainsGPUNodes && ( + + + + + {"Creating GPU nodes..."} + + + + + View Status + + + + + )} + + )} + {match(service.config) + .with({ type: "job" }, () => null) + .with({ type: "predeploy" }, () => null) + .otherwise((config) => ( + <> + + Instances + + + + {!clusterContainsGPUNodes && ( + <> + + Autoscaling + +  (?) + + + + ( + { + onChange({ + ...value, + value: !value.value, + }); + }} + disabled={value.readOnly} + disabledTooltip={ + "You may only edit this field in your porter.yaml." + } + > + + Enable autoscaling (overrides instances) + + + )} + /> + + )} + {autoscalingEnabled.value && ( + <> + + + + + + ( + { + onChange({ + ...value, + value: e, + }); + }} + disabledTooltip={ + value?.readOnly + ? "You may only edit this field in your porter.yaml." + : "Enable autoscaling to specify CPU threshold." + } + /> + )} + /> + + ( + { + onChange({ + ...value, + value: e, + }); + }} + disabledTooltip={ + value?.readOnly + ? "You may only edit this field in your porter.yaml." + : "Enable autoscaling to specify RAM threshold." + } + /> + )} + /> + + )} + + ))} ); }; @@ -442,42 +473,40 @@ const Resources: React.FC = ({ export default Resources; const StyledIcon = styled.i` - cursor: pointer; - font-size: 16px; - margin-right : 5px; - &:hover { - color: #666; + cursor: pointer; + font-size: 16px; + margin-right: 5px; + &:hover { + color: #666; } - `; +`; const SmartOptHeader = styled.div` - display: flex; - align-items: center; - justify-content: flex-end; - ` + 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; - cursor: ${props => (props.hasMessage ? 'pointer' : 'default')}; - background: ${props => props.theme.clickable.bg}; - - `; + 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}; - `; + display: flex; + align-items: center; + padding: 10px; + background: ${(props) => props.theme.clickable.bg}; +`; const TagIcon = styled.img` - height: 12px; - margin-right: 3px; - `; + height: 12px; + margin-right: 3px; +`;