diff --git a/deploy-web/src/components/sdl/AdvancedConfig.tsx b/deploy-web/src/components/sdl/AdvancedConfig.tsx index 8ca49e781..3d0cde631 100644 --- a/deploy-web/src/components/sdl/AdvancedConfig.tsx +++ b/deploy-web/src/components/sdl/AdvancedConfig.tsx @@ -1,5 +1,4 @@ -import { ReactNode, useImperativeHandle, forwardRef, useState } from "react"; -import { makeStyles } from "tss-react/mui"; +import { ReactNode, useState } from "react"; import { Control } from "react-hook-form"; import { Box, Button, Collapse, Paper, Typography, useTheme } from "@mui/material"; import { RentGpusFormValues, Service } from "@src/types"; @@ -21,49 +20,12 @@ type Props = { children?: ReactNode; }; -export type AdvancedConfigRefType = { - // _removeAttribute: (index: number | number[]) => void; -}; - -const useStyles = makeStyles()(theme => ({ - editLink: { - color: theme.palette.secondary.light, - textDecoration: "underline", - cursor: "pointer", - fontWeight: "normal", - fontSize: ".8rem" - }, - formValue: { - color: theme.palette.grey[500] - } -})); - -export const AdvancedConfig = forwardRef(({ control, currentService, providerAttributesSchema }, ref) => { - const { classes } = useStyles(); +export const AdvancedConfig: React.FunctionComponent = ({ control, currentService, providerAttributesSchema }) => { const theme = useTheme(); const [expanded, setIsAdvancedOpen] = useState(false); const [isEditingCommands, setIsEditingCommands] = useState(false); const [isEditingEnv, setIsEditingEnv] = useState(false); const [isEditingExpose, setIsEditingExpose] = useState(false); - // const { - // fields: attributes, - // remove: removeAttribute, - // append: appendAttribute - // } = useFieldArray({ - // control, - // name: `services.${serviceIndex}.placement.attributes`, - // keyName: "id" - // }); - - // const onAddAttribute = () => { - // appendAttribute({ id: nanoid(), key: "", value: "" }); - // }; - - useImperativeHandle(ref, () => ({ - // _removeAttribute(index: number | number[]) { - // removeAttribute(index); - // } - })); return ( @@ -128,4 +90,4 @@ export const AdvancedConfig = forwardRef(({ contro ); -}); +}; diff --git a/deploy-web/src/components/sdl/CpuFormControl.tsx b/deploy-web/src/components/sdl/CpuFormControl.tsx index cf28d2474..475711b22 100644 --- a/deploy-web/src/components/sdl/CpuFormControl.tsx +++ b/deploy-web/src/components/sdl/CpuFormControl.tsx @@ -8,6 +8,7 @@ import { FormPaper } from "./FormPaper"; import { Control, Controller } from "react-hook-form"; import SpeedIcon from "@mui/icons-material/Speed"; import { cx } from "@emotion/css"; +import { validationConfig } from "../shared/akash/units"; type Props = { serviceIndex: number; @@ -41,10 +42,10 @@ export const CpuFormControl: React.FunctionComponent = ({ control, servic if (currentService.count === 1 && _value < 0.1) { return "Minimum amount of CPU for a single service instance is 0.1."; - } else if (currentService.count === 1 && _value > 256) { - return "Maximum amount of CPU for a single service instance is 256."; - } else if (currentService.count > 1 && currentService.count * _value > 512) { - return "Maximum total amount of CPU for a single service instance group is 512."; + } else if (currentService.count === 1 && _value > validationConfig.maxCpuAmount) { + return `Maximum amount of CPU for a single service instance is ${validationConfig.maxCpuAmount}.`; + } else if (currentService.count > 1 && currentService.count * _value > validationConfig.maxGroupCpuCount) { + return `Maximum total amount of CPU for a single service instance group is ${validationConfig.maxGroupCpuCount}.`; } return true; @@ -75,7 +76,7 @@ export const CpuFormControl: React.FunctionComponent = ({ control, servic The amount of vCPU's required for this workload.

- The maximum for a single instance is 256 vCPU's. + The maximum for a single instance is {validationConfig.maxCpuAmount} vCPU's.

The maximum total multiplied by the count of instances is 512 vCPU's. @@ -93,7 +94,7 @@ export const CpuFormControl: React.FunctionComponent = ({ control, servic error={!!fieldState.error} value={field.value || ""} onChange={event => field.onChange(parseFloat(event.target.value))} - inputProps={{ min: 0.1, max: 256, step: 0.1 }} + inputProps={{ min: 0.1, max: validationConfig.maxCpuAmount, step: 0.1 }} size="small" sx={{ width: "100px", marginLeft: "1rem" }} /> @@ -102,7 +103,7 @@ export const CpuFormControl: React.FunctionComponent = ({ control, servic = ({ - + ; providerAttributesSchema: ProviderAttributesSchema; + currentService: Service; }; const useStyles = makeStyles()(theme => ({ @@ -29,7 +31,7 @@ const useStyles = makeStyles()(theme => ({ } })); -export const GpuFormControl: React.FunctionComponent = ({ providerAttributesSchema, control, serviceIndex, hasGpu }) => { +export const GpuFormControl: React.FunctionComponent = ({ providerAttributesSchema, control, serviceIndex, hasGpu, currentService }) => { const { classes } = useStyles(); const theme = useTheme(); @@ -41,7 +43,15 @@ export const GpuFormControl: React.FunctionComponent = ({ providerAttribu rules={{ validate: v => { if (!v) return "GPU amount is required."; - else if (v < 1) return "GPU amount must be greater than 0."; + + const _value = v || 0; + + if (_value < 1) return "GPU amount must be greater than 0."; + else if (currentService.count === 1 && _value > validationConfig.maxGpuAmount) { + return `Maximum amount of GPU for a single service instance is ${validationConfig.maxGpuAmount}.`; + } else if (currentService.count > 1 && currentService.count * _value > validationConfig.maxGroupGpuCount) { + return `Maximum total amount of GPU for a single service instance group is ${validationConfig.maxGroupGpuCount}.`; + } return true; } }} @@ -102,7 +112,7 @@ export const GpuFormControl: React.FunctionComponent = ({ providerAttribu value={field.value || ""} error={!!fieldState.error} onChange={event => field.onChange(parseFloat(event.target.value))} - inputProps={{ min: 1, step: 1 }} + inputProps={{ min: 1, step: 1, max: validationConfig.maxGpuAmount }} size="small" sx={{ width: "100px" }} /> @@ -114,7 +124,7 @@ export const GpuFormControl: React.FunctionComponent = ({ providerAttribu ; currentService: Service; @@ -46,9 +27,8 @@ const useStyles = makeStyles()(theme => ({ })); export const ImageSelect: React.FunctionComponent = ({ control, currentService, onSelectTemplate }) => { - const { classes } = useStyles(); const theme = useTheme(); - const { isLoadingTemplates, gpuTemplates } = useGpuTemplates(); + const { gpuTemplates } = useGpuTemplates(); const [hoveredTemplate, setHoveredTemplate] = useState(null); const [selectedTemplate, setSelectedTemplate] = useState(null); const [popperWidth, setPopperWidth] = useState(null); @@ -57,7 +37,6 @@ export const ImageSelect: React.FunctionComponent = ({ control, currentSe const [anchorEl, setAnchorEl] = useState(null); const filteredGpuTemplates = gpuTemplates.filter(x => x.name.includes(currentService.image)); const open = Boolean(anchorEl) && filteredGpuTemplates.length > 0; - // TODO Width of the popper based on the textfield useEffect(() => { // Populate ref list @@ -93,8 +72,6 @@ export const ImageSelect: React.FunctionComponent = ({ control, currentSe } onClose(); - - // TODO Select current template } if (event.key === "ArrowUp") { diff --git a/deploy-web/src/components/sdl/MemoryFormControl.tsx b/deploy-web/src/components/sdl/MemoryFormControl.tsx index a662d7d11..26b031d46 100644 --- a/deploy-web/src/components/sdl/MemoryFormControl.tsx +++ b/deploy-web/src/components/sdl/MemoryFormControl.tsx @@ -8,7 +8,7 @@ import { FormPaper } from "./FormPaper"; import { Control, Controller } from "react-hook-form"; import { cx } from "@emotion/css"; import MemoryIcon from "@mui/icons-material/Memory"; -import { maxGroupMemory, maxMemory, memoryUnits, minMemory } from "../shared/akash/units"; +import { validationConfig, memoryUnits } from "../shared/akash/units"; type Props = { serviceIndex: number; @@ -41,11 +41,11 @@ export const MemoryFormControl: React.FunctionComponent = ({ control, ser const currentUnit = memoryUnits.find(u => currentService.profile.ramUnit === u.suffix); const _value = (v || 0) * currentUnit.value; - if (currentService.count === 1 && _value < minMemory) { + if (currentService.count === 1 && _value < validationConfig.minMemory) { return "Minimum amount of memory for a single service instance is 1 Mi."; - } else if (currentService.count === 1 && currentService.count * _value > maxMemory) { + } else if (currentService.count === 1 && currentService.count * _value > validationConfig.maxMemory) { return "Maximum amount of memory for a single service instance is 512 Gi."; - } else if (currentService.count > 1 && currentService.count * _value > maxGroupMemory) { + } else if (currentService.count > 1 && currentService.count * _value > validationConfig.maxGroupMemory) { return "Maximum total amount of memory for a single service instance group is 1024 Gi."; } diff --git a/deploy-web/src/components/sdl/RentGpusForm.tsx b/deploy-web/src/components/sdl/RentGpusForm.tsx index 7d9138d4b..d1c980a40 100644 --- a/deploy-web/src/components/sdl/RentGpusForm.tsx +++ b/deploy-web/src/components/sdl/RentGpusForm.tsx @@ -34,6 +34,8 @@ import { ProviderAttributeSchemaDetailValue } from "@src/types/providerAttribute import { importSimpleSdl } from "@src/utils/sdl/sdlImport"; import { ImageSelect } from "./ImageSelect"; import RocketLaunchIcon from "@mui/icons-material/RocketLaunch"; +import { event } from "nextjs-google-analytics"; +import { AnalyticsEvents } from "@src/utils/analytics"; const yaml = require("js-yaml"); @@ -206,10 +208,10 @@ export const RentGpusForm: React.FunctionComponent = ({}) => { saveDeploymentManifestAndName(dd.deploymentId.dseq, sdl, dd.version, address, currentService.image); router.push(UrlService.newDeployment({ step: RouteStepKeys.createLeases, dseq: dd.deploymentId.dseq })); - // event(AnalyticsEvents.CREATE_DEPLOYMENT, { - // category: "deployments", - // label: "Create deployment in wizard" - // }); + event(AnalyticsEvents.CREATE_GPU_DEPLOYMENT, { + category: "deployments", + label: "Create deployment rent gpu form" + }); } else { setIsCreatingDeployment(false); } @@ -246,7 +248,13 @@ export const RentGpusForm: React.FunctionComponent = ({}) => { - + diff --git a/deploy-web/src/components/sdl/SimpleServiceFormControl.tsx b/deploy-web/src/components/sdl/SimpleServiceFormControl.tsx index bcca9bd92..f9dd04218 100644 --- a/deploy-web/src/components/sdl/SimpleServiceFormControl.tsx +++ b/deploy-web/src/components/sdl/SimpleServiceFormControl.tsx @@ -308,6 +308,7 @@ export const SimpleServiceFormControl: React.FunctionComponent = ({ providerAttributesSchema={providerAttributesSchema} serviceIndex={serviceIndex} hasGpu={currentService.profile.hasGpu} + currentService={currentService} /> diff --git a/deploy-web/src/components/sdl/StorageFormControl.tsx b/deploy-web/src/components/sdl/StorageFormControl.tsx index e978c7ec0..5df7c0d55 100644 --- a/deploy-web/src/components/sdl/StorageFormControl.tsx +++ b/deploy-web/src/components/sdl/StorageFormControl.tsx @@ -7,7 +7,7 @@ import InfoIcon from "@mui/icons-material/Info"; import { FormPaper } from "./FormPaper"; import { Control, Controller } from "react-hook-form"; import { cx } from "@emotion/css"; -import { maxStorage, minStorage, storageUnits } from "../shared/akash/units"; +import { validationConfig, storageUnits } from "../shared/akash/units"; import StorageIcon from "@mui/icons-material/Storage"; type Props = { @@ -40,9 +40,9 @@ export const StorageFormControl: React.FunctionComponent = ({ control, se const currentUnit = storageUnits.find(u => currentService.profile.storageUnit === u.suffix); const _value = (v || 0) * currentUnit.value; - if (currentService.count * _value < minStorage) { + if (currentService.count * _value < validationConfig.minStorage) { return "Minimum amount of storage for a single service instance is 5 Mi."; - } else if (currentService.count * _value > maxStorage) { + } else if (currentService.count * _value > validationConfig.maxStorage) { return "Maximum amount of storage for a single service instance is 32 Ti."; } diff --git a/deploy-web/src/components/shared/akash/units.ts b/deploy-web/src/components/shared/akash/units.ts index babc8b670..70843a169 100644 --- a/deploy-web/src/components/shared/akash/units.ts +++ b/deploy-web/src/components/shared/akash/units.ts @@ -1,8 +1,15 @@ -export const minMemory = 1024; // 1 Mi -export const minStorage = 5 * 1024; // 5 Mi -export const maxMemory = 512 * 1024 ** 3; // 512 Gi -export const maxGroupMemory = 1024 * 1024 ** 3; // 1024 Gi -export const maxStorage = 32 * 1024 ** 4; // 32 Ti +// https://github.com/akash-network/akash-api/blob/ea71fbd0bee740198034bf1b0261c90baea88be0/go/node/deployment/v1beta3/validation_config.go +export const validationConfig = { + maxCpuAmount: 256, + maxGroupCpuCount: 512, + maxGpuAmount: 100, + maxGroupGpuCount: 512, + minMemory: 1024, // 1 Mi + minStorage: 5 * 1024, // 5 Mi + maxMemory: 512 * 1024 ** 3, // 512 Gi + maxGroupMemory: 1024 * 1024 ** 3, // 1024 Gi + maxStorage: 32 * 1024 ** 4 // 32 Ti +}; export const memoryUnits = [ { id: 3, suffix: "Mb", value: 1000 ** 2 }, diff --git a/deploy-web/src/pages/rent-gpu/index.tsx b/deploy-web/src/pages/rent-gpu/index.tsx index 475d6a33e..563dc75fb 100644 --- a/deploy-web/src/pages/rent-gpu/index.tsx +++ b/deploy-web/src/pages/rent-gpu/index.tsx @@ -1,4 +1,3 @@ -import { useTheme } from "@mui/material/styles"; import Layout from "@src/components/layout/Layout"; import { Title } from "@src/components/shared/Title"; import PageContainer from "@src/components/shared/PageContainer"; @@ -11,8 +10,6 @@ import { RentGpusForm } from "@src/components/sdl/RentGpusForm"; type Props = {}; const RentGpuPage: React.FunctionComponent = ({}) => { - const theme = useTheme(); - return ( = ({}) => { - {/* */} ); diff --git a/deploy-web/src/utils/analytics.ts b/deploy-web/src/utils/analytics.ts index 3eee5e96c..da6196450 100644 --- a/deploy-web/src/utils/analytics.ts +++ b/deploy-web/src/utils/analytics.ts @@ -17,6 +17,7 @@ export enum AnalyticsEvents { CREATE_LEASE = "create_lease", SEND_MANIFEST = "send_manifest", CREATE_DEPLOYMENT = "create_deployment", + CREATE_GPU_DEPLOYMENT = "create_gpu_deployment", AUTHORIZE_SPEND = "authorize_spend", NAVIGATE_TAB = "navigate_tab_", // Append tab