From 2fafc8049fd3284de3c83b189a50470f310b794a Mon Sep 17 00:00:00 2001 From: Philip Zingmark Date: Tue, 10 Mar 2026 19:18:34 +0100 Subject: [PATCH] update deployment creating to include visibility specs and gpu + divide into components --- src/api/deploy/deployments.ts | 12 +- .../create/EnvironmentVariableSelector.tsx | 155 +++++++ src/components/create/GpuSelector.tsx | 236 ++++++++++ .../create/PersistentVolumeSelector.tsx | 222 +++++++++ src/components/create/SpecSelector.tsx | 185 ++++++++ src/components/create/VisibilitySelector.tsx | 63 +++ src/locales/en.json | 8 +- src/locales/se.json | 8 +- src/pages/create/CreateDeployment.tsx | 430 ++++-------------- src/types.ts | 5 + 10 files changed, 968 insertions(+), 356 deletions(-) create mode 100644 src/components/create/EnvironmentVariableSelector.tsx create mode 100644 src/components/create/GpuSelector.tsx create mode 100644 src/components/create/PersistentVolumeSelector.tsx create mode 100644 src/components/create/SpecSelector.tsx create mode 100644 src/components/create/VisibilitySelector.tsx diff --git a/src/api/deploy/deployments.ts b/src/api/deploy/deployments.ts index 610aa929..6ff567b2 100644 --- a/src/api/deploy/deployments.ts +++ b/src/api/deploy/deployments.ts @@ -1,4 +1,4 @@ -import { Job } from "../../types"; +import { DeploymentSpecsGPU, Job, Visibility } from "../../types"; export const getDeployment = async (token: string, id: string) => { const url = `${import.meta.env.VITE_DEPLOY_API_URL}/deployments/${id}`; @@ -90,7 +90,9 @@ export const createDeployment = async ( imageArgs: any, envs: any, volumes: any, - token: string + token: string, + visibility: Visibility, + specs?: DeploymentSpecsGPU ) => { let body: any = { name, @@ -99,8 +101,14 @@ export const createDeployment = async ( if (zone) body = { ...body, zone }; if (image) body = { ...body, image }; if (imageArgs) body = { ...body, args: imageArgs }; + if (visibility) body = { ...body, visibility }; if (envs) body = { ...body, envs }; if (volumes) body = { ...body, volumes }; + if (specs?.cpuCores) body = { ...body, cpuCores: specs.cpuCores }; + if (specs?.ram) body = { ...body, ram: specs.ram }; + if (specs?.replicas != undefined) + body = { ...body, replicas: specs.replicas }; + if (specs?.gpus) body = { ...body, gpus: specs.gpus }; const res = await fetch( import.meta.env.VITE_DEPLOY_API_URL + "/deployments", diff --git a/src/components/create/EnvironmentVariableSelector.tsx b/src/components/create/EnvironmentVariableSelector.tsx new file mode 100644 index 00000000..4872576e --- /dev/null +++ b/src/components/create/EnvironmentVariableSelector.tsx @@ -0,0 +1,155 @@ +import { + Card, + CardContent, + CardHeader, + IconButton, + Paper, + Stack, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + TextField, +} from "@mui/material"; +import { NoWrapTable as Table } from "../../components/NoWrapTable"; +import Iconify from "../Iconify"; +import { Dispatch, SetStateAction } from "react"; +import { EnvVar } from "../../types"; +import { useTranslation } from "react-i18next"; + +export type EnvironmentVariableSelectorProps = { + envs: EnvVar[]; + setEnvs: Dispatch>; + currentEnv: EnvVar; + setCurrentEnv: Dispatch>; +}; + +export default function EnvironmentVariableSelector({ + envs, + setEnvs, + currentEnv, + setCurrentEnv, +}: EnvironmentVariableSelectorProps) { + const { t } = useTranslation(); + + return ( + + + + + + + + {t("create-deployment-env-key")} + {t("create-deployment-env-value")} + {t("admin-actions")} + + + + {envs.map((env) => ( + + + {env.name} + + + {env.value} + + + + { + setCurrentEnv({ name: env.name, value: env.value }); + setEnvs( + envs.filter((item) => item.name !== env.name) + ); + }} + > + + + + + setEnvs(envs.filter((item) => item.name !== env.name)) + } + > + + + + + + ))} + + + + { + setCurrentEnv({ ...currentEnv, name: e.target.value }); + }} + /> + + + { + setCurrentEnv({ ...currentEnv, value: e.target.value }); + }} + fullWidth + /> + + + { + if (!(currentEnv.name != "" && currentEnv.value != "")) + return; + + setEnvs([...envs, currentEnv]); + + setCurrentEnv({ name: "", value: "" }); + }} + > + + + + + +
+
+
+
+ ); +} diff --git a/src/components/create/GpuSelector.tsx b/src/components/create/GpuSelector.tsx new file mode 100644 index 00000000..9437a935 --- /dev/null +++ b/src/components/create/GpuSelector.tsx @@ -0,0 +1,236 @@ +import { Dispatch, SetStateAction, useState } from "react"; +import { DeploymentGPU } from "../../types"; +import { useTranslation } from "react-i18next"; +import useResource from "../../hooks/useResource"; +import { + Card, + CardContent, + CardHeader, + Typography, + IconButton, + TableBody, + TableRow, + TableHead, + TableCell, + Autocomplete, + TextField, + Stack, + Tooltip, +} from "@mui/material"; +import { NoWrapTable as Table } from "../../components/NoWrapTable"; +import Iconify from "../Iconify"; +import { enqueueSnackbar } from "notistack"; + +export type GPUSelectorProps = { + gpus: DeploymentGPU[]; + setGpus: Dispatch>; + zone: string; +}; + +export default function GPUSelector({ gpus, setGpus, zone }: GPUSelectorProps) { + const { t } = useTranslation(); + const { gpuClaims, zones, user } = useResource(); + + const [selectedOption, setSelectedOption] = useState(null); + const usage = ((user?.usage as any)?.gpus as number) || 0 + gpus.length; + + const selectedZone = zones.find((z) => z.name === zone); + + const draCapableZone = + (selectedZone?.enabled && + selectedZone?.capabilities.some((cap) => cap === "dra")) || + false; + + const availableGpuClaims = gpuClaims?.filter((c) => c.zone === zone) || []; + + const addGpu = (claimName: string, gpuName: string) => { + if (usage + 1 > (((user?.quota as any)?.gpus as number) || 1)) { + enqueueSnackbar(t("quota-exceeded"), { variant: "error" }); + return; + } + setGpus((prev) => { + if (prev.some((g) => g.claimName === claimName && g.name === gpuName)) + return prev; + + return [ + ...prev, + { + name: gpuName, + claimName, + }, + ]; + }); + }; + + const removeGpu = (index: number) => { + setGpus((prev) => prev.filter((_, i) => i !== index)); + }; + + const gpuOptions = availableGpuClaims.flatMap((claim) => { + if (!claim.requested) return []; + + return Object.entries(claim.requested).map(([requestName, req]) => ({ + claimName: claim.name, + requestName, + deviceClass: req.deviceClassName, + vendor: getVendor(req.deviceClassName), + label: `${claim.name} / ${requestName}`, + })); + }); + + return ( + + + + {t("deployment-gpu-subheader")} + +
+
+ + {t("deployment-gpu-quota")} + +
+
+ + {t("deployment-gpu-unstable")} + + + } + > + + + + + } + /> + + + {draCapableZone ? ( + <> + + + + {t("deployment-gpu-claim-name")} + {t("deployment-gpu-name")} + {t("deployment-gpu-vendor")} + {t("deployment-gpu-count")} + {t("admin-actions")} + + + + {gpus.length > 0 ? ( + gpus.map((gpu, index) => { + const claim = availableGpuClaims.find( + (c) => c.name === gpu.claimName + ); + + return ( + + {gpu.claimName} + {gpu.name} + + {getVendor( + claim?.requested?.[gpu.name].deviceClassName + )} + + + {claim?.requested?.[gpu.name].allocationMode === + "ExactCount" + ? claim?.requested?.[gpu.name].count || "1" + : "all"} + + + + removeGpu(index)} + > + + + + + + ); + }) + ) : ( + + + + {t("nothing-to-see-here")} + + + + )} + +
+ + + {t("deployment-gpu-add")} + + + { + if (!value) return; + + addGpu(value.claimName, value.requestName); + setSelectedOption(null); + }} + getOptionLabel={(o) => o.label} + renderInput={(params) => ( + + )} + renderOption={(props, option) => ( +
  • + + +
    + {option.claimName} + + {option.requestName} • {option.vendor} + +
    +
  • + )} + sx={{ mt: 1 }} + /> + + ) : ( + + {t("deployment-gpu-create-select-zone-not-capable")} + + )} +
    +
    + ); +} + +const getVendor = (deviceClassName?: string) => { + if (!deviceClassName) return "unknown"; + + if (deviceClassName.includes("nvidia")) return "NVIDIA"; + if (deviceClassName.includes("amd")) return "AMD"; + if (deviceClassName.includes("intel")) return "Intel"; + + return "GPU"; +}; diff --git a/src/components/create/PersistentVolumeSelector.tsx b/src/components/create/PersistentVolumeSelector.tsx new file mode 100644 index 00000000..c070d8c6 --- /dev/null +++ b/src/components/create/PersistentVolumeSelector.tsx @@ -0,0 +1,222 @@ +import { + Card, + CardContent, + CardHeader, + FormControlLabel, + IconButton, + Paper, + Stack, + Switch, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + TextField, +} from "@mui/material"; +import { NoWrapTable as Table } from "../../components/NoWrapTable"; +import Iconify from "../Iconify"; +import { Dispatch, SetStateAction } from "react"; +import { Volume } from "@kthcloud/go-deploy-types/types/v2/body"; +import { useTranslation } from "react-i18next"; + +export type PersistentVolumeSelectorProps = { + usePersistent: boolean; + setUsePersistent: Dispatch>; + volumes: Volume[]; + setVolumes: Dispatch>; + currentVolume: Volume; + setCurrentVolume: Dispatch>; +}; + +export default function PersistentVolumeSelector({ + usePersistent, + setUsePersistent, + volumes, + setVolumes, + currentVolume, + setCurrentVolume, +}: PersistentVolumeSelectorProps) { + const { t } = useTranslation(); + return ( + + + + setUsePersistent(e.target.checked)} + inputProps={{ "aria-label": "controlled" }} + /> + } + label={t("create-deployment-persistent")} + /> + {usePersistent && ( + + + + + {t("admin-name")} + {t("create-deployment-app-path")} + {t("create-deployment-storage-path")} + {t("admin-actions")} + + + + {volumes.map((persistentRecord) => ( + + + + {persistentRecord.name} + + + + + {persistentRecord.appPath} + + + + + {persistentRecord.serverPath} + + + + + { + setCurrentVolume(persistentRecord); + + setVolumes( + volumes.filter( + (item) => item.name !== persistentRecord.name + ) + ); + }} + > + + + + setVolumes( + volumes.filter( + (item) => item.name !== persistentRecord.name + ) + ) + } + > + + + + + + ))} + + + + { + setCurrentVolume({ + ...currentVolume, + name: e.target.value, + }); + }} + /> + + + { + setCurrentVolume({ + ...currentVolume, + appPath: e.target.value, + }); + }} + fullWidth + /> + + + { + setCurrentVolume({ + ...currentVolume, + serverPath: e.target.value, + }); + }} + fullWidth + /> + + + { + if ( + !( + currentVolume.appPath && + currentVolume.serverPath && + currentVolume.name + ) + ) + return; + + setVolumes([...volumes, currentVolume]); + + setCurrentVolume({ + name: "", + appPath: "", + serverPath: "", + }); + }} + > + + + + + +
    +
    + )} +
    +
    + ); +} diff --git a/src/components/create/SpecSelector.tsx b/src/components/create/SpecSelector.tsx new file mode 100644 index 00000000..87825b5f --- /dev/null +++ b/src/components/create/SpecSelector.tsx @@ -0,0 +1,185 @@ +import { Dispatch, SetStateAction, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { + Card, + CardContent, + CardHeader, + Grid, + TextField, + InputAdornment, +} from "@mui/material"; +import useResource from "../../hooks/useResource"; + +export type SpecSelectorProps = { + cpuCores: number; // per replica + ram: number; // per replica + replicas: number; + + setCpuCores: Dispatch>; + setRam: Dispatch>; + setReplicas: Dispatch>; +}; + +export default function SpecSelector({ + cpuCores, + ram, + replicas, + setCpuCores, + setRam, + setReplicas, +}: SpecSelectorProps) { + const { t } = useTranslation(); + const { user } = useResource(); + + const [cpuInput, setCpuInput] = useState(cpuCores.toString()); + const [ramInput, setRamInput] = useState(ram.toString()); + const [replicasInput, setReplicasInput] = useState( + replicas.toString() + ); + + const [cpuError, setCpuError] = useState(""); + const [ramError, setRamError] = useState(""); + const [replicasError, setReplicasError] = useState(""); + + const maxCpu = user?.quota.cpuCores || 4; + const maxRam = user?.quota.ram || 8; + + const validateQuota = ( + type: "cpu" | "ram" | "replicas", + value: number, + total?: number + ): string => { + switch (type) { + case "cpu": + if (total! + (user?.usage.cpuCores || 0) > maxCpu) { + return `Total CPU (${total}) exceeds quota (${maxCpu})`; + } + break; + + case "ram": + if (total! + (user?.usage.ram || 0) > maxRam) { + return `Total RAM (${total}) exceeds quota (${maxRam} GB)`; + } + break; + + case "replicas": + if (value < 0) return "Replicas cannot be negative"; + break; + } + return ""; + }; + + const handleCpuBlur = () => { + const value = parseFloat(cpuInput); + if (isNaN(value) || value < 0.1) { + setCpuError("CPU must be ≥ 0.1"); + return; + } + const error = validateQuota("cpu", value, value * replicas); + setCpuError(error); + if (!error) setCpuCores(value); + }; + + const handleRamBlur = () => { + const value = parseFloat(ramInput); + if (isNaN(value) || value < 0.1) { + setRamError("RAM must be ≥ 0.1"); + return; + } + const error = validateQuota("ram", value, value * replicas); + setRamError(error); + if (!error) setRam(value); + }; + + const handleReplicasBlur = () => { + const value = parseInt(replicasInput); + if (isNaN(value) || value < 0) { + setReplicasError("Replicas must be ≥ 0"); + return; + } + const coresInput = parseFloat(cpuInput); + if (isNaN(coresInput) || coresInput < 0.1) { + setCpuError("CPU must be ≥ 0.1"); + return; + } + const ramUInput = parseFloat(ramInput); + if (isNaN(ramUInput) || ramUInput < 0.1) { + setRamError("RAM must be ≥ 0.1"); + return; + } + + const cpuTotal = coresInput * value; + const ramTotal = ramUInput * value; + + const cpuErr = validateQuota("cpu", coresInput, cpuTotal); + const ramErr = validateQuota("ram", ramUInput, ramTotal); + const repErr = validateQuota("replicas", value); + + setCpuError(cpuErr); + setRamError(ramErr); + setReplicasError(repErr); + + if (!cpuErr && !ramErr && !repErr) setReplicas(value); + }; + + return ( + + + + + + setCpuInput(e.target.value)} + onBlur={handleCpuBlur} + error={!!cpuError} + helperText={cpuError} + inputProps={{ step: 0.1, min: 0.1 }} + fullWidth + InputProps={{ + endAdornment: ( + cores + ), + }} + /> + + + + setRamInput(e.target.value)} + onBlur={handleRamBlur} + error={!!ramError} + helperText={ramError} + inputProps={{ step: 0.1, min: 0.1 }} + fullWidth + InputProps={{ + endAdornment: ( + GB + ), + }} + /> + + + + setReplicasInput(e.target.value)} + onBlur={handleReplicasBlur} + error={!!replicasError} + helperText={replicasError} + inputProps={{ step: 1, min: 0 }} + fullWidth + /> + + + + + ); +} diff --git a/src/components/create/VisibilitySelector.tsx b/src/components/create/VisibilitySelector.tsx new file mode 100644 index 00000000..07cbf3be --- /dev/null +++ b/src/components/create/VisibilitySelector.tsx @@ -0,0 +1,63 @@ +import { Dispatch, SetStateAction } from "react"; +import { Visibility } from "../../types"; +import { + Card, + CardContent, + CardHeader, + ToggleButton, + ToggleButtonGroup, + Tooltip, +} from "@mui/material"; +import { useTranslation } from "react-i18next"; + +export type VisibilitySelectorProps = { + visibility: Visibility; + setVisibility: Dispatch>; +}; + +export default function VisibilitySelector({ + visibility, + setVisibility, +}: VisibilitySelectorProps) { + const { t } = useTranslation(); + const handleChange = ( + _: React.MouseEvent, + value: Visibility + ) => { + setVisibility(value); + }; + return ( + + + + + + + + {t("admin-visibility-public")} + + + + + <>{t("admin-visibility-auth")} + + + + + {t("admin-visibility-private")} + + + + + + ); +} diff --git a/src/locales/en.json b/src/locales/en.json index 0c2e387f..30811d54 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -565,17 +565,23 @@ "use-vms": "Use VMs", "landing-hero-gpus": "GPUs (deployment)", "deployment-gpu": "GPU", + "deployment-gpu-claim-name": "Claim", + "deployment-gpu-name": "GPU", + "deployment-gpu-vendor": "Vendor", + "deployment-gpu-count": "Count", "deployment-gpu-configuration": "Configure GPUs for deployment", "deployment-gpu-none": "No GPUs", "deployment-gpu-add": "Add GPUs", "deployment-gpu-subheader": "GPUs on deployments utilizes a new k8s feature called DRA combined with driver specific features to share GPUs between multiple containers.", "deployment-gpu-quota": "GPU quota is user global, if your quota allows multiple GPUs you can allocate them how you like, multiple on a single deployment or single allocation for different deployments.", "deployment-gpu-unstable": "This feature relies on BETA features from NVIDIA, and issues might occur. Feel free to reach out on discord if you have any questions.", + "deployment-gpu-create-select-title": "Add GPU resources", "clusters-overview": "Clusters", "active-keys": "Active keys", "expired-keys": "Expired keys", "delete-all-expired": "Delete all expired", "custom-deployment": "Custom", - "custom-deployment-description": "Build and use your own image, use CI/CD through Github actions / Gitlab workflows or build and push your own image. Secrets will be provided after the deployment is created" + "custom-deployment-description": "Build and use your own image, use CI/CD through Github actions / Gitlab workflows or build and push your own image. Secrets will be provided after the deployment is created", + "deployment-spec-selector": "Select specs" } } diff --git a/src/locales/se.json b/src/locales/se.json index 5700c64f..f52908ac 100644 --- a/src/locales/se.json +++ b/src/locales/se.json @@ -566,17 +566,23 @@ "use-vms": "Använda VMar", "landing-hero-gpus": "GPUs (deployment)", "deployment-gpu": "GPU", + "deployment-gpu-claim-name": "Claim", + "deployment-gpu-name": "GPU", + "deployment-gpu-vendor": "Tillverkare", + "deployment-gpu-count": "Antal", "deployment-gpu-configuration": "Konfigurera GPUer för deployment", "deployment-gpu-none": "Inga GPUer", "deployment-gpu-add": "Lägg till GPUer", "deployment-gpu-subheader": "GPUs på deployments använder en ny k8s funktion som heter DRA kombinerat med drivrutin funktioner för att dela ett grafikkort mellan flera containrar.", "deployment-gpu-quota": "GPU användnings kvoten är användarglobal, om din kvot tillåter flera GPUer kan du dela upp dom som du vill. Dvs antingen ett grafikkort per deployment, eller flera GPUer på en deployment.", "deployment-gpu-unstable": "Denna funktion bygger på funktioner från NVIDIA som är i BETA, fel kan uppstå. Kontakta oss på discord om du har några frågor.", + "deployment-gpu-create-select-title": "Lägg till GPU resurser", "clusters-overview": "Kluster", "active-keys": "Aktiva nycklar", "expired-keys": "Utgångna nycklar", "delete-all-expired": "Ta bort alla utgågna", "custom-deployment": "Egen", - "custom-deployment-description": "Bygg och använd din egna image, använd CI/CD via Github actions / Gitlab workflows eller bygg och pusha din egna image. Nycklar går att se när din deployment är skapad" + "custom-deployment-description": "Bygg och använd din egna image, använd CI/CD via Github actions / Gitlab workflows eller bygg och pusha din egna image. Nycklar går att se när din deployment är skapad", + "deployment-spec-selector": "Välj specifikationer" } } diff --git a/src/pages/create/CreateDeployment.tsx b/src/pages/create/CreateDeployment.tsx index 92aca644..10e8f739 100644 --- a/src/pages/create/CreateDeployment.tsx +++ b/src/pages/create/CreateDeployment.tsx @@ -1,23 +1,6 @@ // @mui -import { - Button, - TextField, - Card, - CardContent, - CardHeader, - TableCell, - TableContainer, - TableHead, - TableRow, - TableBody, - IconButton, - Paper, - Stack, - Switch, - FormControlLabel, -} from "@mui/material"; +import { Button, Card, CardContent, CardHeader, Stack } from "@mui/material"; import { useState } from "react"; -import Iconify from "../../components/Iconify"; import { createDeployment } from "../../api/deploy/deployments"; import { useSnackbar } from "notistack"; import { useKeycloak } from "../../hooks/useKeycloak"; @@ -28,8 +11,13 @@ import useResource from "../../hooks/useResource"; import ZoneSelector from "./ZoneSelector"; import { useTranslation } from "react-i18next"; import { Volume } from "@kthcloud/go-deploy-types/types/v2/body"; -import { NoWrapTable as Table } from "../../components/NoWrapTable"; import DeploymentTypeCard from "./DeploymentTypeCard"; +import { DeploymentGPU, EnvVar, Visibility } from "../../types"; +import EnvironmentVariableSelector from "../../components/create/EnvironmentVariableSelector"; +import PersistentVolumeSelector from "../../components/create/PersistentVolumeSelector"; +import GPUSelector from "../../components/create/GpuSelector"; +import SpecSelector from "../../components/create/SpecSelector"; +import VisibilitySelector from "../../components/create/VisibilitySelector"; export default function CreateDeployment({ finished, @@ -61,15 +49,23 @@ export default function CreateDeployment({ const [image, setImage] = useState(""); const [imageArgs, setImageArgs] = useState(""); - const [envs, setEnvs] = useState([{ name: "PORT", value: "8080" }]); - const [newEnvName, setNewEnvName] = useState(""); - const [newEnvValue, setNewEnvValue] = useState(""); + const [visibility, setVisibility] = useState("public"); + + const [envs, setEnvs] = useState([{ name: "PORT", value: "8080" }]); + const [currentEnv, setCurrentEnv] = useState({ name: "", value: "" }); const [usePersistent, setUsePersistent] = useState(false); const [persistent, setPersistent] = useState([]); - const [newPersistentName, setNewPersistentName] = useState(""); - const [newPersistentAppPath, setNewPersistentAppPath] = useState(""); - const [newPersistentServerPath, setNewPersistentServerPath] = useState(""); + const [currentVolume, setCurrentVolume] = useState({ + name: "", + appPath: "", + serverPath: "", + }); + + const [cpuCores, setCpuCores] = useState(0.2); + const [ram, setRam] = useState(0.5); + const [replicas, setReplicas] = useState(1); + const [gpus, setGpus] = useState([]); const [initialName, setInitialName] = useState( import.meta.env.VITE_RELEASE_BRANCH @@ -83,34 +79,21 @@ export default function CreateDeployment({ let newEnvs = envs; // Apply unsaved ENVS - if (newEnvName && newEnvValue) { - newEnvs = [ - ...envs, - { - name: newEnvName, - value: newEnvValue, - }, - ]; - - setNewEnvName(""); - setNewEnvValue(""); + if (currentEnv.name != "" && currentEnv.value != "") { + newEnvs = [...envs, currentEnv]; + setCurrentEnv({ name: "", value: "" }); } let newPersistent = persistent; // Apply unsaved persitent - if (newPersistentName && newPersistentAppPath && newPersistentServerPath) { - newPersistent = [ - ...persistent, - { - name: newPersistentName, - appPath: newPersistentAppPath, - serverPath: newPersistentServerPath, - }, - ]; - - setNewPersistentName(""); - setNewPersistentAppPath(""); - setNewPersistentServerPath(""); + if ( + currentVolume.name && + currentVolume.appPath && + currentVolume.serverPath + ) { + newPersistent = [...persistent, currentVolume]; + + setCurrentVolume({ name: "", appPath: "", serverPath: "" }); } // If args are "", it should be an empty array @@ -125,7 +108,14 @@ export default function CreateDeployment({ newImageArgs, newEnvs, newPersistent, - keycloak.token + keycloak.token, + visibility, + { + cpuCores, + ram, + replicas, + gpus, + } ); finished(job, stay); if (stay) { @@ -135,8 +125,17 @@ export default function CreateDeployment({ ); setCleaned(""); setEnvs([]); - setNewEnvName(""); - setNewEnvValue(""); + + setCurrentEnv({ name: "", value: "" }); + + setUsePersistent(false); + setPersistent([]); + setCurrentVolume({ name: "", appPath: "", serverPath: "" }); + + setGpus([]); + setCpuCores(0.2); + setRam(0.5); + setReplicas(1); } } catch (error: any) { errorHandler(error).forEach((e) => @@ -182,310 +181,37 @@ export default function CreateDeployment({ setImageArgs={setImageArgs} /> - - - - - - - - {t("create-deployment-env-key")} - {t("create-deployment-env-value")} - {t("admin-actions")} - - - - {envs.map((env) => ( - - - {env.name} - - - {env.value} - - - - { - setNewEnvName(env.name); - setNewEnvValue(env.value); - setEnvs( - envs.filter((item) => item.name !== env.name) - ); - }} - > - - - - - setEnvs( - envs.filter((item) => item.name !== env.name) - ) - } - > - - - - - - ))} - - - - { - setNewEnvName(e.target.value); - }} - /> - - - { - setNewEnvValue(e.target.value); - }} - fullWidth - /> - - - { - if (!(newEnvName && newEnvName)) return; - - setEnvs([ - ...envs, - { - name: newEnvName, - value: newEnvValue, - }, - ]); - - setNewEnvName(""); - setNewEnvValue(""); - }} - > - - - - - -
    -
    -
    -
    - - - - - setUsePersistent(e.target.checked)} - inputProps={{ "aria-label": "controlled" }} - /> - } - label={t("create-deployment-persistent")} - /> - {usePersistent && ( - - - - - {t("admin-name")} - {t("create-deployment-app-path")} - {t("create-deployment-storage-path")} - {t("admin-actions")} - - - - {persistent.map((persistentRecord) => ( - - - - {persistentRecord.name} - - - - - {persistentRecord.appPath} - - - - - {persistentRecord.serverPath} - - - - - { - setNewPersistentName(persistentRecord.name); - setNewPersistentAppPath(persistentRecord.appPath); - setNewPersistentServerPath( - persistentRecord.serverPath - ); + - setPersistent( - persistent.filter( - (item) => item.name !== persistentRecord.name - ) - ); - }} - > - - - - setPersistent( - persistent.filter( - (item) => item.name !== persistentRecord.name - ) - ) - } - > - - - - - - ))} + - - - { - setNewPersistentName(e.target.value); - }} - /> - - - { - setNewPersistentAppPath(e.target.value); - }} - fullWidth - /> - - - { - setNewPersistentServerPath(e.target.value); - }} - fullWidth - /> - - - { - if ( - !( - newPersistentAppPath && - newPersistentServerPath && - newPersistentName - ) - ) - return; + - setPersistent([ - ...persistent, - { - name: newPersistentName, - appPath: newPersistentAppPath, - serverPath: newPersistentServerPath, - }, - ]); + - setNewPersistentName(""); - setNewPersistentAppPath(""); - setNewPersistentServerPath(""); - }} - > - - - - - -
    -
    - )} -
    -
    +