From f1503f5c3d5c40843f8335bf619e89571fffe789 Mon Sep 17 00:00:00 2001 From: Maxime Beauchamp <15185355+baktun14@users.noreply.github.com> Date: Fri, 6 Oct 2023 16:50:47 -0400 Subject: [PATCH] features/improve-sdl-builder (#24) * import GPU sdl builder * added gpus to sdl builder * import template with GPU * added support for IP lease sdl builder * fix error * added http options form * added http options to sdl builder * added multiple denom (USDC) to sdl builder * handle loading schema for sdl builder * Apply suggestions from code review --------- Co-authored-by: Maxime Cyr --- deploy-web/package-lock.json | 143 +--------- deploy-web/package.json | 2 +- .../src/components/dashboard/Dashboard.tsx | 18 +- .../src/components/sdl/ExposeFormModal.tsx | 103 +++++-- deploy-web/src/components/sdl/FormSelect.tsx | 92 ++++++ .../components/sdl/HttpOptionsFormControl.tsx | 263 ++++++++++++++++++ .../src/components/sdl/ImportSdlModal.tsx | 17 +- .../src/components/sdl/PlacementFormModal.tsx | 119 +++++--- .../components/sdl/SimpleSdlBuilderForm.tsx | 33 +-- .../sdl/SimpleServiceFormControl.tsx | 163 +++++++++++ .../src/components/sdl/ToFormControl.tsx | 8 +- deploy-web/src/components/shared/UsdLabel.tsx | 17 ++ deploy-web/src/components/shared/akash/gpu.ts | 1 + deploy-web/src/hooks/useDenom.ts | 9 + deploy-web/src/types/providerAttributes.ts | 2 +- deploy-web/src/types/sdlBuilder.ts | 10 +- .../src/utils/deploymentData/v1beta3.ts | 4 +- deploy-web/src/utils/sdl/data.ts | 40 ++- deploy-web/src/utils/sdl/sdlGenerator.ts | 57 +++- deploy-web/src/utils/sdl/sdlImport.ts | 50 +++- 20 files changed, 885 insertions(+), 266 deletions(-) create mode 100644 deploy-web/src/components/sdl/FormSelect.tsx create mode 100644 deploy-web/src/components/sdl/HttpOptionsFormControl.tsx create mode 100644 deploy-web/src/components/shared/UsdLabel.tsx create mode 100644 deploy-web/src/components/shared/akash/gpu.ts diff --git a/deploy-web/package-lock.json b/deploy-web/package-lock.json index 5e2b22b91..e4a80e4f4 100644 --- a/deploy-web/package-lock.json +++ b/deploy-web/package-lock.json @@ -38,7 +38,7 @@ "file-saver": "^2.0.5", "http-proxy": "^1.18.1", "jotai": "^2.0.4", - "js-yaml": "^3.14.1", + "js-yaml": "^4.1.0", "json-stable-stringify": "^1.0.2", "json2csv": "^5.0.7", "jsrsasign": "^10.6.1", @@ -366,11 +366,6 @@ "resolved": "https://registry.npmjs.org/@cosmjs/utils/-/utils-0.28.13.tgz", "integrity": "sha512-dVeMBiyg+46x7XBZEfJK8yTihphbCFpjVYmLJVqmTsHfJwymQ65cpyW/C+V/LgWARGK8hWQ/aX9HM5Ao8QmMSg==" }, - "node_modules/@akashnetwork/akashjs/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" - }, "node_modules/@akashnetwork/akashjs/node_modules/axios": { "version": "0.24.0", "resolved": "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz", @@ -388,17 +383,6 @@ "protobufjs": "~6.11.2" } }, - "node_modules/@akashnetwork/akashjs/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, "node_modules/@ampproject/remapping": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.1.2.tgz", @@ -3040,12 +3024,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/@eslint/eslintrc/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "peer": true - }, "node_modules/@eslint/eslintrc/node_modules/globals": { "version": "13.17.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", @@ -3061,18 +3039,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@eslint/eslintrc/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "peer": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -5966,12 +5932,9 @@ } }, "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dependencies": { - "sprintf-js": "~1.0.2" - } + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "node_modules/aria-query": { "version": "4.2.2", @@ -8272,12 +8235,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/eslint/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "peer": true - }, "node_modules/eslint/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -8348,18 +8305,6 @@ "node": ">=8" } }, - "node_modules/eslint/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "peer": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, "node_modules/eslint/node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -10072,12 +10017,11 @@ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" @@ -13826,11 +13770,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" - }, "node_modules/stacktrace-parser": { "version": "0.1.10", "resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.10.tgz", @@ -16046,11 +15985,6 @@ "resolved": "https://registry.npmjs.org/@cosmjs/utils/-/utils-0.28.13.tgz", "integrity": "sha512-dVeMBiyg+46x7XBZEfJK8yTihphbCFpjVYmLJVqmTsHfJwymQ65cpyW/C+V/LgWARGK8hWQ/aX9HM5Ao8QmMSg==" }, - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" - }, "axios": { "version": "0.24.0", "resolved": "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz", @@ -16067,14 +16001,6 @@ "long": "^4.0.0", "protobufjs": "~6.11.2" } - }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "requires": { - "argparse": "^2.0.1" - } } } }, @@ -18135,12 +18061,6 @@ "uri-js": "^4.2.2" } }, - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "peer": true - }, "globals": { "version": "13.17.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", @@ -18150,15 +18070,6 @@ "type-fest": "^0.20.2" } }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "peer": true, - "requires": { - "argparse": "^2.0.1" - } - }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -20395,12 +20306,9 @@ } }, "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "requires": { - "sprintf-js": "~1.0.2" - } + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "aria-query": { "version": "4.2.2", @@ -22014,12 +21922,6 @@ "color-convert": "^2.0.1" } }, - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "peer": true - }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -22069,15 +21971,6 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "peer": true }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "peer": true, - "requires": { - "argparse": "^2.0.1" - } - }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -23551,12 +23444,11 @@ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "argparse": "^2.0.1" } }, "jsesc": { @@ -26281,11 +26173,6 @@ "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.1.tgz", "integrity": "sha512-ekwEbFp5aqSPKaqeY1PGrlGQxPNaq+Cnx4+bE2D8sciBQrHpbwoBbawqTN2+6jPs9IdWxxiUcN0K2pkczD3zmw==" }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" - }, "stacktrace-parser": { "version": "0.1.10", "resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.10.tgz", diff --git a/deploy-web/package.json b/deploy-web/package.json index 277a24d0e..89b463727 100644 --- a/deploy-web/package.json +++ b/deploy-web/package.json @@ -42,7 +42,7 @@ "file-saver": "^2.0.5", "http-proxy": "^1.18.1", "jotai": "^2.0.4", - "js-yaml": "^3.14.1", + "js-yaml": "^4.1.0", "json-stable-stringify": "^1.0.2", "json2csv": "^5.0.7", "jsrsasign": "^10.6.1", diff --git a/deploy-web/src/components/dashboard/Dashboard.tsx b/deploy-web/src/components/dashboard/Dashboard.tsx index f0c64144d..e8c69ea8f 100644 --- a/deploy-web/src/components/dashboard/Dashboard.tsx +++ b/deploy-web/src/components/dashboard/Dashboard.tsx @@ -23,10 +23,10 @@ import { StatsCard } from "./StatsCard"; import { FormattedDecimalCurrency } from "../shared/FormattedDecimalCurrency"; import { DiffPercentageChip } from "../shared/DiffPercentageChip"; import { useTheme } from "@mui/material"; -import { uaktToAKT } from "@src/utils/priceUtils"; import { BlockRow } from "../blockchain/BlockRow"; import { TransactionRow } from "../blockchain/TransactionRow"; import { useSelectedNetwork } from "@src/hooks/useSelectedNetwork"; +import { USDCLabel, USDLabel } from "../shared/UsdLabel"; interface IDashboardProps { dashboardData: DashboardData; @@ -535,19 +535,3 @@ const AKTLabel = () => { ); }; - -const USDLabel = () => { - return ( - - $USD - - ); -}; - -const USDCLabel = () => { - return ( - - USDC - - ); -}; diff --git a/deploy-web/src/components/sdl/ExposeFormModal.tsx b/deploy-web/src/components/sdl/ExposeFormModal.tsx index f541167dd..a6256cc69 100644 --- a/deploy-web/src/components/sdl/ExposeFormModal.tsx +++ b/deploy-web/src/components/sdl/ExposeFormModal.tsx @@ -1,8 +1,7 @@ import { ReactNode, useRef } from "react"; -import { makeStyles } from "tss-react/mui"; import { Popup } from "../shared/Popup"; import { Control, Controller, useFieldArray } from "react-hook-form"; -import { Box, Grid, IconButton, InputAdornment, MenuItem, Select, TextField, useTheme } from "@mui/material"; +import { Box, Grid, IconButton, InputAdornment, MenuItem, Select, TextField } from "@mui/material"; import { Expose, SdlBuilderFormValues, Service } from "@src/types"; import DeleteIcon from "@mui/icons-material/Delete"; import { AcceptFormControl, AcceptRefType } from "./AcceptFormControl"; @@ -12,6 +11,9 @@ import { protoTypes } from "@src/utils/sdl/data"; import { FormPaper } from "./FormPaper"; import { CustomTooltip } from "../shared/CustomTooltip"; import InfoIcon from "@mui/icons-material/Info"; +import { endpointNameValidationRegex } from "@src/utils/deploymentData/v1beta3"; +import { HttpOptionsFormControl } from "./HttpOptionsFormControl"; +import { ProviderAttributesSchema } from "@src/types/providerAttributes"; type Props = { open: boolean; @@ -21,20 +23,18 @@ type Props = { children?: ReactNode; services: Service[]; expose: Expose[]; + providerAttributesSchema: ProviderAttributesSchema; }; -const useStyles = makeStyles()(theme => ({ - formControl: { - marginBottom: theme.spacing(1.5) - }, - textField: { - width: "100%" - } -})); - -export const ExposeFormModal: React.FunctionComponent = ({ open, control, serviceIndex, onClose, expose: _expose, services }) => { - const { classes } = useStyles(); - const theme = useTheme(); +export const ExposeFormModal: React.FunctionComponent = ({ + open, + control, + serviceIndex, + onClose, + expose: _expose, + services, + providerAttributesSchema +}) => { const acceptRef = useRef(); const toRef = useRef(); const { @@ -82,7 +82,7 @@ export const ExposeFormModal: React.FunctionComponent = ({ open, control, variant="custom" title={ - Edit Expose + Edit Port Expose = ({ open, control, - + @@ -230,6 +230,77 @@ export const ExposeFormModal: React.FunctionComponent = ({ open, control, + + + { + const hasValidChars = endpointNameValidationRegex.test(value); + const hasValidStartingChar = /^[a-z]/.test(value); + const hasValidEndingChar = !value.endsWith("-"); + + if (!hasValidChars) { + return "Invalid ip name. It must only be lower case letters, numbers and dashes."; + } else if (!hasValidStartingChar) { + return "Invalid starting character. It can only start with a lowercase letter."; + } else if (!hasValidEndingChar) { + return "Invalid ending character. It can only end with a lowercase letter or number"; + } + + return true; + } + }} + render={({ field, fieldState }) => ( + field.onChange(event.target.value)} + InputProps={{ + endAdornment: ( + + + Optional. +
+
+ Option for Tenants to request publicly routable IP addresses for the services they deploy +
+
+ + View official documentation. + + + } + > + +
+
+ ) + }} + /> + )} + /> +
+ + + +
{expIndex !== 0 && ( diff --git a/deploy-web/src/components/sdl/FormSelect.tsx b/deploy-web/src/components/sdl/FormSelect.tsx new file mode 100644 index 000000000..629f8dfb6 --- /dev/null +++ b/deploy-web/src/components/sdl/FormSelect.tsx @@ -0,0 +1,92 @@ +import { Autocomplete, Box, ClickAwayListener, TextField } from "@mui/material"; +import { SdlBuilderFormValues } from "@src/types"; +import { ProviderAttributeSchemaDetailValue, ProviderAttributesSchema } from "@src/types/providerAttributes"; +import { useState } from "react"; +import { Control, Controller, FieldPath } from "react-hook-form"; + +type ProviderSelectProps = { + control: Control; + providerAttributesSchema: ProviderAttributesSchema; + optionName?: keyof ProviderAttributesSchema; + name: FieldPath; + className?: string; + requiredMessage?: string; + label: string; + multiple?: boolean; + required?: boolean; + disabled?: boolean; +}; + +export const FormSelect: React.FunctionComponent = ({ + control, + providerAttributesSchema, + optionName, + name, + className, + requiredMessage, + label, + required = providerAttributesSchema[optionName]?.required || false, + multiple, + disabled +}) => { + const [isOpen, setIsOpen] = useState(false); + const options = providerAttributesSchema[optionName]?.values || []; + + return ( + ( + + option?.description} + defaultValue={multiple ? [] : null} + isOptionEqualToValue={(option, value) => option.key === value.key} + filterSelectedOptions + fullWidth + multiple={multiple} + ChipProps={{ size: "small" }} + onChange={(event, newValue: string[] | null | ProviderAttributeSchemaDetailValue[]) => { + field.onChange(newValue); + }} + renderInput={params => ( + setIsOpen(false)}> + setIsOpen(prev => !prev)} + sx={{ minHeight: "42px" }} + /> + + )} + renderOption={(props, option) => { + return ( + +
{option.description}
+
+ ); + }} + /> +
+ )} + /> + ); +}; diff --git a/deploy-web/src/components/sdl/HttpOptionsFormControl.tsx b/deploy-web/src/components/sdl/HttpOptionsFormControl.tsx new file mode 100644 index 000000000..880f3727f --- /dev/null +++ b/deploy-web/src/components/sdl/HttpOptionsFormControl.tsx @@ -0,0 +1,263 @@ +import { ReactNode } from "react"; +import { makeStyles } from "tss-react/mui"; +import { Control, Controller } from "react-hook-form"; +import { Box, Checkbox, FormControlLabel, InputAdornment, MenuItem, Paper, Select, TextField, Typography, useTheme } from "@mui/material"; +import { SdlBuilderFormValues, Service } from "@src/types"; +import InfoIcon from "@mui/icons-material/Info"; +import { CustomTooltip } from "../shared/CustomTooltip"; +import { ProviderAttributesSchema } from "@src/types/providerAttributes"; +import { nextCases } from "@src/utils/sdl/data"; + +type Props = { + serviceIndex: number; + exposeIndex: number; + services: Service[]; + control: Control; + providerAttributesSchema: ProviderAttributesSchema; + children?: ReactNode; +}; + +const useStyles = makeStyles()(theme => ({ + root: { + marginTop: "1rem", + padding: "1rem", + height: "100%", + display: "flex", + flexDirection: "column", + justifyContent: "space-between", + backgroundColor: theme.palette.mode === "dark" ? theme.palette.primary.dark : theme.palette.grey[300] + }, + formControl: { + marginBottom: theme.spacing(1.5) + }, + textField: { + width: "100%" + } +})); + +export const HttpOptionsFormControl: React.FunctionComponent = ({ control, serviceIndex, exposeIndex, services, providerAttributesSchema }) => { + const { classes } = useStyles(); + const theme = useTheme(); + const currentService = services[serviceIndex]; + + return ( + +
+ + + + HTTP Options + + + + Akash deployment SDL services stanza definitions have been augmented to include “http_options” allowing granular specification of HTTP + endpoint parameters. Inclusion of the parameters in this section are optional but will afford detailed definitions of attributes such as + body/payload max size where necessary. +
+
+ + View official documentation. + + + } + > + +
+
+ + + ( + + } + label="Custom Options" + /> + )} + /> + +
+ + {currentService.expose[exposeIndex]?.hasCustomHttpOptions && ( + <> + ( + field.onChange(parseInt(event.target.value))} + inputProps={{ min: 0 }} + InputProps={{ + endAdornment: ( + + + + + + ) + }} + /> + )} + /> + + ( + field.onChange(parseInt(event.target.value))} + inputProps={{ min: 0 }} + InputProps={{ + endAdornment: ( + + + + + + ) + }} + /> + )} + /> + + ( + field.onChange(parseInt(event.target.value))} + inputProps={{ min: 0 }} + InputProps={{ + endAdornment: ( + + + + + + ) + }} + /> + )} + /> + + ( + field.onChange(parseInt(event.target.value))} + inputProps={{ min: 0 }} + InputProps={{ + endAdornment: ( + + + + + + ) + }} + /> + )} + /> + + ( + field.onChange(parseInt(event.target.value))} + inputProps={{ min: 0 }} + InputProps={{ + endAdornment: ( + + + + + + ) + }} + /> + )} + /> + + ( + + )} + /> + + )} +
+
+ ); +}; diff --git a/deploy-web/src/components/sdl/ImportSdlModal.tsx b/deploy-web/src/components/sdl/ImportSdlModal.tsx index bb9e05420..cd217f248 100644 --- a/deploy-web/src/components/sdl/ImportSdlModal.tsx +++ b/deploy-web/src/components/sdl/ImportSdlModal.tsx @@ -1,5 +1,4 @@ import { ReactNode, useEffect, useState } from "react"; -import { makeStyles } from "tss-react/mui"; import { Popup } from "../shared/Popup"; import { Alert, Box, Typography, useTheme } from "@mui/material"; import { useSnackbar } from "notistack"; @@ -12,6 +11,7 @@ import { Snackbar } from "../shared/Snackbar"; import ArrowDownwardIcon from "@mui/icons-material/ArrowDownward"; import { event } from "nextjs-google-analytics"; import { AnalyticsEvents } from "@src/utils/analytics"; +import { useProviderAttributesSchema } from "@src/queries/useProvidersQuery"; type Props = { setValue: UseFormSetValue; @@ -19,21 +19,12 @@ type Props = { children?: ReactNode; }; -const useStyles = makeStyles()(theme => ({ - formControl: { - marginBottom: theme.spacing(1.5) - }, - textField: { - width: "100%" - } -})); - export const ImportSdlModal: React.FunctionComponent = ({ onClose, setValue }) => { - const { classes } = useStyles(); const theme = useTheme(); const [sdl, setSdl] = useState(""); const [parsingError, setParsingError] = useState(null); - const { enqueueSnackbar, closeSnackbar } = useSnackbar(); + const { enqueueSnackbar } = useSnackbar(); + const { data: providerAttributesSchema } = useProviderAttributesSchema(); useEffect(() => { const timer = Timer(500); @@ -54,7 +45,7 @@ export const ImportSdlModal: React.FunctionComponent = ({ onClose, setVal try { if (!sdl) return null; - const services = importSimpleSdl(yamlStr); + const services = importSimpleSdl(yamlStr, providerAttributesSchema); setParsingError(null); diff --git a/deploy-web/src/components/sdl/PlacementFormModal.tsx b/deploy-web/src/components/sdl/PlacementFormModal.tsx index d2ed45ee8..9269c5ab9 100644 --- a/deploy-web/src/components/sdl/PlacementFormModal.tsx +++ b/deploy-web/src/components/sdl/PlacementFormModal.tsx @@ -2,8 +2,8 @@ import { ReactNode, useRef } from "react"; import { makeStyles } from "tss-react/mui"; import { Popup } from "../shared/Popup"; import { Control, Controller } from "react-hook-form"; -import { Box, Grid, InputAdornment, TextField, useTheme } from "@mui/material"; -import { Placement, SdlBuilderFormValues } from "@src/types"; +import { Box, FormControl, Grid, InputAdornment, InputLabel, MenuItem, Select, TextField, useTheme } from "@mui/material"; +import { Placement, SdlBuilderFormValues, Service } from "@src/types"; import { FormPaper } from "./FormPaper"; import { SignedByFormControl, SignedByRefType } from "./SignedByFormControl"; import { AttributesFormControl, AttributesRefType } from "./AttributesFormControl"; @@ -12,10 +12,15 @@ import InfoIcon from "@mui/icons-material/Info"; import { PriceValue } from "../shared/PriceValue"; import { getAvgCostPerMonth } from "@src/utils/priceUtils"; import { uAktDenom } from "@src/utils/constants"; +import { useSdlDenoms } from "@src/hooks/useDenom"; +import { FormattedNumber } from "react-intl"; +import { USDLabel } from "../shared/UsdLabel"; +import { udenomToDenom } from "@src/utils/mathHelpers"; type Props = { open: boolean; serviceIndex: number; + services: Service[]; onClose: () => void; control: Control; children?: ReactNode; @@ -31,11 +36,14 @@ const useStyles = makeStyles()(theme => ({ } })); -export const PlacementFormModal: React.FunctionComponent = ({ open, control, serviceIndex, onClose, placement: _placement }) => { +export const PlacementFormModal: React.FunctionComponent = ({ open, control, services, serviceIndex, onClose, placement: _placement }) => { const { classes } = useStyles(); const theme = useTheme(); const signedByRef = useRef(); const attritubesRef = useRef(); + const supportedSdlDenoms = useSdlDenoms(); + const currentService = services[serviceIndex]; + const selectedDenom = supportedSdlDenoms.find(x => x.value === currentService.placement.pricing.denom); const _onClose = () => { const attributesToRemove = []; @@ -143,50 +151,77 @@ export const PlacementFormModal: React.FunctionComponent = ({ open, contr - + + Token ( - field.onChange(parseFloat(event.target.value))} - InputProps={{ - endAdornment: uAKT - }} - /> - )} + name={`services.${serviceIndex}.placement.pricing.denom`} + defaultValue="" + rules={{ + required: true + }} + render={({ fieldState, field }) => { + return ( + + ); + }} /> - - The maximum amount of uAKT you're willing to pay per block (~6 seconds). -
-
- Akash will only show providers costing less than this amount. -
-
-
+ + + ( + field.onChange(parseFloat(event.target.value))} + /> + )} + /> + + The maximum amount of {selectedDenom?.label} you're willing to pay per block (~6 seconds). +
+
+ Akash will only show providers costing less than{" "} - ~ + {selectedDenom?.value === uAktDenom ? ( + <> + ~ + + ) : ( + <> + + + + + + )} -   per month -
- - } - > - -
-
+  per month + + } + > + + + +
diff --git a/deploy-web/src/components/sdl/SimpleSdlBuilderForm.tsx b/deploy-web/src/components/sdl/SimpleSdlBuilderForm.tsx index 28992a795..8e3a0f8ec 100644 --- a/deploy-web/src/components/sdl/SimpleSdlBuilderForm.tsx +++ b/deploy-web/src/components/sdl/SimpleSdlBuilderForm.tsx @@ -1,7 +1,5 @@ -import { useTheme } from "@mui/material/styles"; import { Alert, Box, Button, CircularProgress, Typography } from "@mui/material"; import { useForm, useFieldArray } from "react-hook-form"; -import { makeStyles } from "tss-react/mui"; import { useEffect, useRef, useState } from "react"; import { nanoid } from "nanoid"; import { ITemplate, SdlBuilderFormValues, Service } from "@src/types"; @@ -24,36 +22,11 @@ import { memoryUnits, storageUnits } from "../shared/akash/units"; import sdlStore from "@src/store/sdlStore"; import { RouteStepKeys } from "@src/utils/constants"; import { useAtom } from "jotai"; - -const useStyles = makeStyles()(theme => ({ - formControl: { - marginBottom: theme.spacing(1.5) - }, - textField: { - width: "100%" - }, - serviceBox: { - marginTop: "1rem", - border: `1px solid ${theme.palette.mode === "dark" ? theme.palette.grey[900] : theme.palette.grey[100]}`, - borderRadius: ".5rem" - }, - editLink: { - color: theme.palette.secondary.light, - textDecoration: "underline", - cursor: "pointer", - fontWeight: "normal", - fontSize: ".8rem" - }, - formValue: { - color: theme.palette.grey[500] - } -})); +import { useProviderAttributesSchema } from "@src/queries/useProvidersQuery"; type Props = {}; export const SimpleSDLBuilderForm: React.FunctionComponent = ({}) => { - const { classes } = useStyles(); - const theme = useTheme(); const [error, setError] = useState(null); const [templateMetadata, setTemplateMetadata] = useState(null); const [serviceCollapsed, setServiceCollapsed] = useState([]); @@ -63,6 +36,7 @@ export const SimpleSDLBuilderForm: React.FunctionComponent = ({}) => { const [, setSdlResult] = useState(null); const formRef = useRef(); const [, setDeploySdl] = useAtom(sdlStore.deploySdl); + const { data: providerAttributesSchema } = useProviderAttributesSchema(); const { enqueueSnackbar } = useSnackbar(); const { handleSubmit, @@ -105,7 +79,7 @@ export const SimpleSDLBuilderForm: React.FunctionComponent = ({}) => { const response = await axios.get(`/api/proxy/user/template/${id}`); const template: ITemplate = response.data; - const services = importSimpleSdl(template.sdl); + const services = importSimpleSdl(template.sdl, providerAttributesSchema); setIsLoadingTemplate(false); @@ -287,6 +261,7 @@ export const SimpleSDLBuilderForm: React.FunctionComponent = ({}) => { service={service} serviceIndex={serviceIndex} _services={_services} + providerAttributesSchema={providerAttributesSchema} control={control} trigger={trigger} onRemoveService={onRemoveService} diff --git a/deploy-web/src/components/sdl/SimpleServiceFormControl.tsx b/deploy-web/src/components/sdl/SimpleServiceFormControl.tsx index ecc6c01e1..0ee51cbf8 100644 --- a/deploy-web/src/components/sdl/SimpleServiceFormControl.tsx +++ b/deploy-web/src/components/sdl/SimpleServiceFormControl.tsx @@ -2,6 +2,7 @@ import { useTheme } from "@mui/material/styles"; import { Box, Checkbox, + CircularProgress, Collapse, FormControl, FormHelperText, @@ -45,12 +46,16 @@ import { averageBlockTime, getAvgCostPerMonth } from "@src/utils/priceUtils"; import { averageDaysInMonth } from "@src/utils/dateUtils"; import Image from "next/legacy/image"; import { uAktDenom } from "@src/utils/constants"; +import { gpuVendors } from "../shared/akash/gpu"; +import { ProviderAttributesSchema } from "@src/types/providerAttributes"; +import { FormSelect } from "./FormSelect"; type Props = { service: Service; _services: Service[]; serviceIndex: number; control: Control; + providerAttributesSchema: ProviderAttributesSchema; trigger: UseFormTrigger; onRemoveService: (index: number) => void; serviceCollapsed: number[]; @@ -86,6 +91,7 @@ export const SimpleServiceFormControl: React.FunctionComponent = ({ serviceIndex, control, _services, + providerAttributesSchema, onRemoveService, trigger, serviceCollapsed, @@ -129,6 +135,7 @@ export const SimpleServiceFormControl: React.FunctionComponent = ({ serviceIndex={serviceIndex} expose={currentService.expose} services={_services} + providerAttributesSchema={providerAttributesSchema} /> {/** Edit Placement */} = ({ onClose={() => setIsEditingPlacement(null)} open={_isEditingPlacement} serviceIndex={serviceIndex} + services={_services} placement={currentService.placement} /> @@ -390,6 +398,155 @@ export const SimpleServiceFormControl: React.FunctionComponent = ({ /> + + + { + if (!v) return "GPU amount is required."; + else if (v < 1) return "GPU amount must be greater than 0."; + return true; + } + }} + render={({ field, fieldState }) => ( + + + + + + GPU + + + The amount of GPUs required for this workload. +
+
+ You can also specify the GPU vendor and model you want specifically. If you don't specify any model, providers with any + GPU model will bid on your workload. +
+
+ + View official documentation. + + + } + > + +
+
+ + ( + + )} + /> +
+ + {currentService.profile.hasGpu && ( + + field.onChange(parseFloat(event.target.value))} + inputProps={{ min: 1, step: 1 }} + size="small" + sx={{ width: "100px" }} + /> + + )} +
+ + {currentService.profile.hasGpu && ( + field.onChange(newValue)} + /> + )} + + {!!fieldState.error && {fieldState.error.message}} +
+ )} + /> + + {currentService.profile.hasGpu && ( +
+ + ( + + )} + /> + + + + {providerAttributesSchema ? ( + + ) : ( + + + + Loading GPU models... + + + )} + +
+ )} +
+
+ = ({ Global   {exp.global ? "True" : "False"} + {exp.ipName && ( +
+ IP Name   + {exp.ipName} +
+ )}
Accept   diff --git a/deploy-web/src/components/sdl/ToFormControl.tsx b/deploy-web/src/components/sdl/ToFormControl.tsx index a8d365ed3..62a4e05d9 100644 --- a/deploy-web/src/components/sdl/ToFormControl.tsx +++ b/deploy-web/src/components/sdl/ToFormControl.tsx @@ -1,7 +1,7 @@ import { ReactNode, useImperativeHandle, forwardRef } from "react"; import { makeStyles } from "tss-react/mui"; import { Control, Controller, useFieldArray } from "react-hook-form"; -import { Box, Button, Checkbox, FormControlLabel, IconButton, MenuItem, Paper, Select, TextField, Typography, useTheme } from "@mui/material"; +import { Box, Button, Checkbox, FormControlLabel, IconButton, MenuItem, Paper, Select, Typography, useTheme } from "@mui/material"; import { SdlBuilderFormValues, Service } from "@src/types"; import DeleteIcon from "@mui/icons-material/Delete"; import { nanoid } from "nanoid"; @@ -29,12 +29,6 @@ const useStyles = makeStyles()(theme => ({ flexDirection: "column", justifyContent: "space-between", backgroundColor: theme.palette.mode === "dark" ? theme.palette.primary.dark : theme.palette.grey[300] - }, - formControl: { - marginBottom: theme.spacing(1.5) - }, - textField: { - width: "100%" } })); diff --git a/deploy-web/src/components/shared/UsdLabel.tsx b/deploy-web/src/components/shared/UsdLabel.tsx new file mode 100644 index 000000000..4fb7c0324 --- /dev/null +++ b/deploy-web/src/components/shared/UsdLabel.tsx @@ -0,0 +1,17 @@ +import { Box } from "@mui/material"; + +export const USDLabel = () => { + return ( + + $USD + + ); +}; + +export const USDCLabel = () => { + return ( + + USDC + + ); +}; diff --git a/deploy-web/src/components/shared/akash/gpu.ts b/deploy-web/src/components/shared/akash/gpu.ts new file mode 100644 index 000000000..6db047495 --- /dev/null +++ b/deploy-web/src/components/shared/akash/gpu.ts @@ -0,0 +1 @@ +export const gpuVendors = [{ id: 1, value: "nvidia" }]; \ No newline at end of file diff --git a/deploy-web/src/hooks/useDenom.ts b/deploy-web/src/hooks/useDenom.ts index 697a2c2d7..83b458865 100644 --- a/deploy-web/src/hooks/useDenom.ts +++ b/deploy-web/src/hooks/useDenom.ts @@ -10,3 +10,12 @@ export const getUsdcDenom = () => { const selectedNetwork = getSelectedNetwork(); return usdcIbcDenoms[selectedNetwork.id]; }; + +export const useSdlDenoms = () => { + const usdcDenom = useUsdcDenom(); + + return [ + { id: "uakt", label: "uAKT", value: "uakt" }, + { id: "uusdc", label: "uUSDC", value: usdcDenom } + ]; +}; diff --git a/deploy-web/src/types/providerAttributes.ts b/deploy-web/src/types/providerAttributes.ts index c2f1e7e7f..230c51b17 100644 --- a/deploy-web/src/types/providerAttributes.ts +++ b/deploy-web/src/types/providerAttributes.ts @@ -70,4 +70,4 @@ export type ProviderAttributeSchemaDetail = { values?: Array; }; -export type ProviderAttributeSchemaDetailValue = { key: "string"; description: string; value?: any }; +export type ProviderAttributeSchemaDetailValue = { key: string; description: string; value?: any }; diff --git a/deploy-web/src/types/sdlBuilder.ts b/deploy-web/src/types/sdlBuilder.ts index b25db9918..a4b630c5d 100644 --- a/deploy-web/src/types/sdlBuilder.ts +++ b/deploy-web/src/types/sdlBuilder.ts @@ -1,3 +1,5 @@ +import { ProviderAttributeSchemaDetailValue } from "./providerAttributes"; + export type Service = { id: string; title: string; @@ -24,6 +26,10 @@ export type ImportService = { export type Profile = { cpu: number; + hasGpu?: boolean; + gpu?: number; + gpuVendor?: string; + gpuModels?: ProviderAttributeSchemaDetailValue[]; ram: number; ramUnit: string; storage: number; @@ -32,7 +38,6 @@ export type Profile = { persistentStorage?: number; persistentStorageUnit?: string; persistentStorageParam?: ServicePersistentStorage; - ipName?: string; }; export type ServicePersistentStorage = { @@ -62,7 +67,9 @@ export type Expose = { to?: To[]; global?: boolean; accept?: Accept[]; + hasCustomHttpOptions?: boolean; httpOptions?: ServiceExposeHTTPOptions; + ipName?: string; }; export type To = { @@ -102,6 +109,7 @@ export type Placement = { pricing: { // profile: string; amount: number; + denom: string; }; }; diff --git a/deploy-web/src/utils/deploymentData/v1beta3.ts b/deploy-web/src/utils/deploymentData/v1beta3.ts index efcc788e6..c604dd954 100644 --- a/deploy-web/src/utils/deploymentData/v1beta3.ts +++ b/deploy-web/src/utils/deploymentData/v1beta3.ts @@ -4,8 +4,8 @@ import { stringToBoolean } from "../stringUtils"; import path from "path"; import { getUsdcDenom } from "@src/hooks/useDenom"; -const endpointNameValidationRegex = /^[a-z]+[-_\da-z]+$/; -const endpointKindIP = "ip"; +export const endpointNameValidationRegex = /^[a-z]+[-_\da-z]+$/; +export const endpointKindIP = "ip"; function validate(yamlJson) { const sdl = getSdl(yamlJson, "beta3"); diff --git a/deploy-web/src/utils/sdl/data.ts b/deploy-web/src/utils/sdl/data.ts index 50f67612a..25306dc89 100644 --- a/deploy-web/src/utils/sdl/data.ts +++ b/deploy-web/src/utils/sdl/data.ts @@ -7,12 +7,25 @@ export const protoTypes = [ { id: 3, name: "tcp" } ]; +export const defaultHttpOptions = { + maxBodySize: 1048576, + readTimeout: 60000, + sendTimeout: 60000, + nextCases: ["error", "timeout"], + nextTries: 3, + nextTimeout: 60000 +}; + export const defaultService: Service = { id: nanoid(), title: "service-1", image: "", profile: { cpu: 0.1, + gpu: 1, + gpuVendor: "nvidia", + gpuModels: [], + hasGpu: false, ram: 512, ramUnit: "Mi", storage: 1, @@ -34,7 +47,16 @@ export const defaultService: Service = { proto: "http", global: true, to: [], - accept: [] + accept: [], + ipName: "", + httpOptions: { + maxBodySize: defaultHttpOptions.maxBodySize, + readTimeout: defaultHttpOptions.readTimeout, + sendTimeout: defaultHttpOptions.sendTimeout, + nextCases: defaultHttpOptions.nextCases, + nextTries: defaultHttpOptions.nextTries, + nextTimeout: defaultHttpOptions.nextTimeout + } } ], command: { command: "", arg: "" }, @@ -42,7 +64,8 @@ export const defaultService: Service = { placement: { name: "dcloud", pricing: { - amount: 1000 + amount: 1000, + denom: "uakt" }, signedBy: { anyOf: [], @@ -52,3 +75,16 @@ export const defaultService: Service = { }, count: 1 }; + +export const nextCases = [ + { id: 1, value: "error" }, + { id: 2, value: "timeout" }, + { id: 3, value: "403" }, + { id: 4, value: "404" }, + { id: 5, value: "429" }, + { id: 6, value: "500" }, + { id: 7, value: "502" }, + { id: 8, value: "503" }, + { id: 9, value: "504" }, + { id: 10, value: "off" } +]; diff --git a/deploy-web/src/utils/sdl/sdlGenerator.ts b/deploy-web/src/utils/sdl/sdlGenerator.ts index 78bf08714..eb092445a 100644 --- a/deploy-web/src/utils/sdl/sdlGenerator.ts +++ b/deploy-web/src/utils/sdl/sdlGenerator.ts @@ -1,5 +1,6 @@ import { Expose, SdlBuilderFormValues } from "@src/types"; import yaml from "js-yaml"; +import { defaultHttpOptions } from "./data"; export const generateSdl = (formData: SdlBuilderFormValues) => { const sdl = { version: "2.0", services: {}, profiles: { compute: {}, placement: {} }, deployment: {} }; @@ -7,6 +8,8 @@ export const generateSdl = (formData: SdlBuilderFormValues) => { formData.services.forEach(service => { sdl.services[service.title] = { image: service.image, + + // Expose expose: service.expose.map(e => { // Port const _expose = { port: e.port }; @@ -32,10 +35,23 @@ export const generateSdl = (formData: SdlBuilderFormValues) => { const to = e.to.map(to => ({ ["service"]: to.value })); _expose["to"] = [ { - global: !!e.global + global: !!e.global, + ...(e.ipName ? { ip: e.ipName } : {}) } ].concat(to as any); + // HTTP Options + if (e.hasCustomHttpOptions) { + _expose["http_options"] = { + max_body_size: e.httpOptions?.maxBodySize ?? defaultHttpOptions.maxBodySize, + read_timeout: e.httpOptions?.readTimeout ?? defaultHttpOptions.readTimeout, + send_timeout: e.httpOptions?.sendTimeout ?? defaultHttpOptions.sendTimeout, + next_cases: e.httpOptions?.nextCases ?? defaultHttpOptions.nextCases, + next_tries: e.httpOptions?.nextTries ?? defaultHttpOptions.nextTries, + next_timeout: e.httpOptions?.nextTimeout ?? defaultHttpOptions.nextTimeout + }; + } + return _expose; }) }; @@ -69,6 +85,25 @@ export const generateSdl = (formData: SdlBuilderFormValues) => { } }; + // GPU + if (service.profile.hasGpu) { + sdl.profiles.compute[service.title].resources.gpu = { + units: service.profile.gpu, + attributes: { + vendor: { + [service.profile.gpuVendor]: + service.profile.gpuModels.length > 0 + ? service.profile.gpuModels.map(x => { + const modelKey = x.key.split("/"); + // capabilities/gpu/vendor/nvidia/model/h100 -> h100 + return { model: modelKey[modelKey.length - 1] }; + }) + : null + } + } + }; + } + // Persistent Storage if (service.profile.hasPersistentStorage) { sdl.services[service.title].params = { @@ -115,6 +150,20 @@ export const generateSdl = (formData: SdlBuilderFormValues) => { sdl.profiles.placement[service.placement.name].attributes = service.placement.attributes.reduce((acc, curr) => ((acc[curr.key] = curr.value), acc), {}); } + // IP Lease + if (service.expose.some(exp => exp.ipName)) { + sdl["endpoints"] = {}; + + service.expose + .filter(exp => exp.ipName) + .forEach(exp => { + sdl["endpoints"][exp.ipName] = { + kind: "ip" + }; + }); + } + + // Count sdl.deployment[service.title] = { [service.placement.name]: { profile: service.title, @@ -124,7 +173,11 @@ export const generateSdl = (formData: SdlBuilderFormValues) => { }); const result = yaml.dump(sdl, { - indent: 2 + indent: 2, + quotingType: '"', + styles: { + "!!null": "empty" // dump null as emtpy value + } }); return `--- diff --git a/deploy-web/src/utils/sdl/sdlImport.ts b/deploy-web/src/utils/sdl/sdlImport.ts index 007236200..d36dc72f0 100644 --- a/deploy-web/src/utils/sdl/sdlImport.ts +++ b/deploy-web/src/utils/sdl/sdlImport.ts @@ -3,8 +3,10 @@ import { nanoid } from "nanoid"; import { capitalizeFirstLetter } from "../stringUtils"; import yaml from "js-yaml"; import { CustomValidationError } from "../deploymentData"; +import { ProviderAttributeSchemaDetailValue, ProviderAttributesSchema } from "@src/types/providerAttributes"; +import { defaultHttpOptions } from "./data"; -export const importSimpleSdl = yamlStr => { +export const importSimpleSdl = (yamlStr: string, providerAttributesSchema: ProviderAttributesSchema) => { try { const yamlJson = yaml.load(yamlStr) as any; const services: ImportService[] = []; @@ -29,6 +31,10 @@ export const importSimpleSdl = yamlStr => { // Service compute profile service.profile = { cpu: compute.resources.cpu.units, + gpu: compute.resources.gpu ? compute.resources.gpu.units : 1, + gpuVendor: compute.resources.gpu ? getGpuVendor(compute.resources.gpu.attributes.vendor) : "nvidia", + gpuModels: compute.resources.gpu ? getGpuModels(compute.resources.gpu.attributes.vendor, providerAttributesSchema) : [], + hasGpu: !!compute.resources.gpu, ram: getResourceDigit(compute.resources.memory.size), ramUnit: getResourceUnit(compute.resources.memory.size), storage: getResourceDigit(ephStorage.size), @@ -65,9 +71,17 @@ export const importSimpleSdl = yamlStr => { proto: expose.proto === "tcp" ? expose.proto : "http", global: !!isGlobal, to: expose.to.filter(t => t.global === undefined).map(t => ({ id: nanoid(), value: t.service })), - accept: expose.accept?.map(a => ({ id: nanoid(), value: a })) || [] - // httpOptions: "TODO" - // ip: "TODO" + accept: expose.accept?.map(a => ({ id: nanoid(), value: a })) || [], + ipName: isGlobal?.ip ? isGlobal.ip : "", + hasCustomHttpOptions: !!expose.http_options, + httpOptions: { + maxBodySize: expose.http_options?.max_body_size ?? defaultHttpOptions.maxBodySize, + readTimeout: expose.http_options?.read_timeout ?? defaultHttpOptions.readTimeout, + sendTimeout: expose.http_options?.send_timeout ?? defaultHttpOptions.sendTimeout, + nextCases: expose.http_options?.next_cases ?? defaultHttpOptions.nextCases, + nextTries: expose.http_options?.next_tries ?? defaultHttpOptions.nextTries, + nextTimeout: expose.http_options?.next_timeout ?? defaultHttpOptions.nextTimeout + } }; service.expose.push(_expose); @@ -90,7 +104,8 @@ export const importSimpleSdl = yamlStr => { service.placement = { name: placementName, pricing: { - amount: placementPricing.amount + amount: placementPricing.amount, + denom: placementPricing.denom }, signedBy: { anyOf: placement.signedBy && placement.signedBy?.anyOf ? placement.signedBy.anyOf.map(x => ({ id: nanoid(), value: x })) : [], @@ -128,3 +143,28 @@ const getResourceDigit = (size: string): number => { const getResourceUnit = (size: string): string => { return capitalizeFirstLetter(size.match(/[a-zA-Z]+/g)[0]); }; + +const getGpuVendor = (vendorKey: { [key: string]: any }): string => { + const vendor = Object.keys(vendorKey)[0]; + + // For now only nvidia is supported + return vendor || "nvidia"; +}; + +const getGpuModels = ( + vendor: { [key: string]: { model: string }[] }, + providerAttributesSchema: ProviderAttributesSchema +): ProviderAttributeSchemaDetailValue[] => { + const models = vendor.nvidia + .map(m => { + const model = providerAttributesSchema["hardware-gpu-model"].values.find(v => { + const modelKey = v.key.split("/"); + // capabilities/gpu/vendor/nvidia/model/h100 -> h100 + return m.model === modelKey[modelKey.length - 1]; + }) as ProviderAttributeSchemaDetailValue; + return model; + }) + .filter(m => m); + + return models; +};