Forráskód Böngészése

Add gpu + instance types (#4310)

Feroze Mohideen 2 éve
szülő
commit
84d482c2c1

+ 3 - 0
api/server/handlers/project_integration/preflight_check.go

@@ -1,6 +1,7 @@
 package project_integration
 
 import (
+	"fmt"
 	"net/http"
 
 	"connectrpc.com/connect"
@@ -109,6 +110,8 @@ func (p *CreatePreflightCheckHandler) ServeHTTP(w http.ResponseWriter, r *http.R
 		return
 	}
 
+	fmt.Printf("here is the checkResp: %v\n", checkResp.Msg.PreflightChecks)
+
 	errors := []PreflightCheckError{}
 	for key, val := range checkResp.Msg.PreflightChecks {
 		if val.Message == "" || !contains(recognizedPreflightCheckKeys, key) {

+ 161 - 2
dashboard/src/lib/clusters/constants.ts

@@ -99,336 +99,451 @@ const SUPPORTED_AWS_MACHINE_TYPES: ClientMachineType[] = [
     name: "t3.medium",
     displayName: "t3.medium",
     supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "t3.large",
     displayName: "t3.large",
     supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "t3.xlarge",
     displayName: "t3.xlarge",
     supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "t3.2xlarge",
     displayName: "t3.2xlarge",
     supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "t3a.medium",
     displayName: "t3a.medium",
     supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "t3a.large",
     displayName: "t3a.large",
     supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "t3a.xlarge",
     displayName: "t3a.xlarge",
     supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "t3a.2xlarge",
     displayName: "t3a.2xlarge",
     supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "t4g.medium",
     displayName: "t4g.medium",
     supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "t4g.large",
     displayName: "t4g.large",
     supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "t4g.xlarge",
     displayName: "t4g.xlarge",
     supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "t4g.2xlarge",
     displayName: "t4g.2xlarge",
     supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "c6i.large",
     displayName: "c6i.large",
     supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "c6i.xlarge",
     displayName: "c6i.xlarge",
     supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "c6i.2xlarge",
     displayName: "c6i.2xlarge",
     supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "c6i.4xlarge",
     displayName: "c6i.4xlarge",
     supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "c6i.8xlarge",
     displayName: "c6i.8xlarge",
     supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "c6a.large",
     displayName: "c6a.large",
     supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "c6a.2xlarge",
     displayName: "c6a.2xlarge",
     supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "c6a.4xlarge",
     displayName: "c6a.4xlarge",
     supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "c6a.8xlarge",
     displayName: "c6a.8xlarge",
     supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "r6i.large",
     displayName: "r6i.large",
     supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "r6i.xlarge",
     displayName: "r6i.xlarge",
     supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "r6i.2xlarge",
     displayName: "r6i.2xlarge",
     supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "r6i.4xlarge",
     displayName: "r6i.4xlarge",
     supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "r6i.8xlarge",
     displayName: "r6i.8xlarge",
     supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "r6i.12xlarge",
     displayName: "r6i.12xlarge",
     supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "r6i.16xlarge",
     displayName: "r6i.16xlarge",
     supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "r6i.24xlarge",
     displayName: "r6i.24xlarge",
     supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "r6i.32xlarge",
     displayName: "r6i.32xlarge",
     supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "m5n.large",
     displayName: "m5n.large",
     supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "m5n.xlarge",
     displayName: "m5n.xlarge",
     supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "m5n.2xlarge",
     displayName: "m5n.2xlarge",
     supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "m6a.large",
     displayName: "m6a.large",
     supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "m6a.xlarge",
     displayName: "m6a.xlarge",
     supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "m6a.2xlarge",
     displayName: "m6a.2xlarge",
     supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "m6a.4xlarge",
     displayName: "m6a.4xlarge",
     supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "m6a.8xlarge",
     displayName: "m6a.8xlarge",
     supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "m6a.12xlarge",
     displayName: "m6a.12xlarge",
     supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "m7a.medium",
     displayName: "m7a.medium",
     supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "m7a.large",
     displayName: "m7a.large",
     supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "m7a.xlarge",
     displayName: "m7a.xlarge",
     supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "m7a.2xlarge",
     displayName: "m7a.2xlarge",
     supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "m7a.4xlarge",
     displayName: "m7a.4xlarge",
     supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "m7a.8xlarge",
     displayName: "m7a.8xlarge",
     supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "m7a.12xlarge",
     displayName: "m7a.12xlarge",
     supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "m7a.16xlarge",
     displayName: "m7a.16xlarge",
     supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "m7a.24xlarge",
     displayName: "m7a.24xlarge",
     supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "m7i.large",
     displayName: "m7i.large",
     supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "m7i.xlarge",
     displayName: "m7i.xlarge",
     supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "m7i.2xlarge",
     displayName: "m7i.2xlarge",
     supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "m7i.4xlarge",
     displayName: "m7i.4xlarge",
     supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "m7i.8xlarge",
     displayName: "m7i.8xlarge",
     supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "m7i.12xlarge",
     displayName: "m7i.12xlarge",
     supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "c7a.medium",
     displayName: "c7a.medium",
     supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "c7a.large",
     displayName: "c7a.large",
     supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "c7a.xlarge",
     displayName: "c7a.xlarge",
     supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "c7a.2xlarge",
     displayName: "c7a.2xlarge",
     supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "c7a.4xlarge",
     displayName: "c7a.4xlarge",
     supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "c7a.8xlarge",
     displayName: "c7a.8xlarge",
     supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "c7a.12xlarge",
     displayName: "c7a.12xlarge",
     supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "c7a.16xlarge",
     displayName: "c7a.16xlarge",
     supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "c7a.24xlarge",
     displayName: "c7a.24xlarge",
     supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
+  },
+  {
+    name: "c7g.medium",
+    displayName: "c7g.medium",
+    supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
+  },
+  {
+    name: "c7g.large",
+    displayName: "c7g.large",
+    supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
+  },
+  {
+    name: "c7g.xlarge",
+    displayName: "c7g.xlarge",
+    supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
+  },
+  {
+    name: "c7g.2xlarge",
+    displayName: "c7g.2xlarge",
+    supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
+  },
+  {
+    name: "c7g.4xlarge",
+    displayName: "c7g.4xlarge",
+    supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
+  },
+  {
+    name: "c7g.8xlarge",
+    displayName: "c7g.8xlarge",
+    supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
+  },
+  {
+    name: "c7g.12xlarge",
+    displayName: "c7g.12xlarge",
+    supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
+  },
+  {
+    name: "c7g.16xlarge",
+    displayName: "c7g.16xlarge",
+    supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "g4dn.xlarge",
     displayName: "g4dn.xlarge",
     supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: true,
   },
   {
     name: "g4dn.2xlarge",
     displayName: "g4dn.2xlarge",
     supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: true,
   },
   {
     name: "g4dn.4xlarge",
     displayName: "g4dn.4xlarge",
     supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: true,
   },
   {
     name: "p4d.24xlarge",
     displayName: "p4d.24xlarge",
     supportedRegions: SUPPORTED_AWS_REGIONS.map((r) => r.name),
+    isGPU: true,
   },
 ];
 
@@ -437,156 +552,187 @@ const SUPPORTED_GCP_MACHINE_TYPES: ClientMachineType[] = [
     name: "e2-standard-2",
     displayName: "e2-standard-2",
     supportedRegions: SUPPORTED_GCP_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "e2-standard-4",
     displayName: "e2-standard-4",
     supportedRegions: SUPPORTED_GCP_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "e2-standard-8",
     displayName: "e2-standard-8",
     supportedRegions: SUPPORTED_GCP_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "e2-standard-16",
     displayName: "e2-standard-16",
     supportedRegions: SUPPORTED_GCP_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "e2-standard-32",
     displayName: "e2-standard-32",
     supportedRegions: SUPPORTED_GCP_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "c3-standard-4",
     displayName: "c3-standard-4",
     supportedRegions: SUPPORTED_GCP_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "c3-standard-8",
     displayName: "c3-standard-8",
     supportedRegions: SUPPORTED_GCP_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "c3-standard-22",
     displayName: "c3-standard-22",
     supportedRegions: SUPPORTED_GCP_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "c3-standard-44",
     displayName: "c3-standard-44",
     supportedRegions: SUPPORTED_GCP_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "c3-highcpu-4",
     displayName: "c3-highcpu-4",
     supportedRegions: SUPPORTED_GCP_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "c3-highcpu-8",
     displayName: "c3-highcpu-8",
     supportedRegions: SUPPORTED_GCP_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "c3-highcpu-22",
     displayName: "c3-highcpu-22",
     supportedRegions: SUPPORTED_GCP_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "c3-highcpu-44",
     displayName: "c3-highcpu-44",
     supportedRegions: SUPPORTED_GCP_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "c3-highmem-4",
     displayName: "c3-highmem-4",
     supportedRegions: SUPPORTED_GCP_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "c3-highmem-8",
     displayName: "c3-highmem-8",
     supportedRegions: SUPPORTED_GCP_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "c3-highmem-22",
     displayName: "c3-highmem-22",
     supportedRegions: SUPPORTED_GCP_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "c3-highmem-44",
     displayName: "c3-highmem-44",
     supportedRegions: SUPPORTED_GCP_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "n1-standard-1",
     displayName: "n1-standard-1",
     supportedRegions: SUPPORTED_GCP_REGIONS.map((r) => r.name),
+    isGPU: true,
   },
   {
     name: "n1-standard-2",
     displayName: "n1-standard-2",
     supportedRegions: SUPPORTED_GCP_REGIONS.map((r) => r.name),
+    isGPU: true,
   },
   {
     name: "n1-standard-4",
     displayName: "n1-standard-4",
     supportedRegions: SUPPORTED_GCP_REGIONS.map((r) => r.name),
+    isGPU: true,
   },
   {
     name: "n1-standard-8",
     displayName: "n1-standard-8",
     supportedRegions: SUPPORTED_GCP_REGIONS.map((r) => r.name),
+    isGPU: true,
   },
   {
     name: "n1-standard-16",
     displayName: "n1-standard-16",
     supportedRegions: SUPPORTED_GCP_REGIONS.map((r) => r.name),
+    isGPU: true,
   },
   {
     name: "n1-standard-32",
     displayName: "n1-standard-32",
     supportedRegions: SUPPORTED_GCP_REGIONS.map((r) => r.name),
+    isGPU: false,
   },
   {
     name: "n1-highmem-2",
     displayName: "n1-highmem-2",
     supportedRegions: SUPPORTED_GCP_REGIONS.map((r) => r.name),
+    isGPU: true,
   },
   {
     name: "n1-highmem-4",
     displayName: "n1-highmem-4",
     supportedRegions: SUPPORTED_GCP_REGIONS.map((r) => r.name),
+    isGPU: true,
   },
   {
     name: "n1-highmem-8",
     displayName: "n1-highmem-8",
     supportedRegions: SUPPORTED_GCP_REGIONS.map((r) => r.name),
+    isGPU: true,
   },
   {
     name: "n1-highmem-16",
     displayName: "n1-highmem-16",
     supportedRegions: SUPPORTED_GCP_REGIONS.map((r) => r.name),
+    isGPU: true,
   },
   {
     name: "n1-highmem-32",
     displayName: "n1-highmem-32",
     supportedRegions: SUPPORTED_GCP_REGIONS.map((r) => r.name),
+    isGPU: true,
   },
   {
     name: "n1-highcpu-8",
     displayName: "n1-highcpu-8",
     supportedRegions: SUPPORTED_GCP_REGIONS.map((r) => r.name),
+    isGPU: true,
   },
   {
     name: "n1-highcpu-16",
     displayName: "n1-highcpu-16",
     supportedRegions: SUPPORTED_GCP_REGIONS.map((r) => r.name),
+    isGPU: true,
   },
   {
     name: "n1-highcpu-32",
     displayName: "n1-highcpu-32",
     supportedRegions: SUPPORTED_GCP_REGIONS.map((r) => r.name),
+    isGPU: true,
   },
 ];
 
@@ -616,6 +762,7 @@ const SUPPORTED_AZURE_MACHINE_TYPES: ClientMachineType[] = [
       "westus2",
       "westus3",
     ],
+    isGPU: false,
   },
   {
     name: "Standard_B2as_v2",
@@ -642,6 +789,7 @@ const SUPPORTED_AZURE_MACHINE_TYPES: ClientMachineType[] = [
       "westus2",
       "westus3",
     ],
+    isGPU: false,
   },
   {
     name: "Standard_A2_v2",
@@ -662,6 +810,7 @@ const SUPPORTED_AZURE_MACHINE_TYPES: ClientMachineType[] = [
       "uaenorth",
       "uksouth",
     ],
+    isGPU: false,
   },
   {
     name: "Standard_A4_v2",
@@ -682,6 +831,7 @@ const SUPPORTED_AZURE_MACHINE_TYPES: ClientMachineType[] = [
       "uaenorth",
       "uksouth",
     ],
+    isGPU: false,
   },
   {
     name: "Standard_DS1_v2",
@@ -702,6 +852,7 @@ const SUPPORTED_AZURE_MACHINE_TYPES: ClientMachineType[] = [
       "uaenorth",
       "uksouth",
     ],
+    isGPU: false,
   },
   {
     name: "Standard_DS2_v2",
@@ -725,6 +876,7 @@ const SUPPORTED_AZURE_MACHINE_TYPES: ClientMachineType[] = [
       "switzerlandnorth",
       "westus3",
     ],
+    isGPU: false,
   },
   {
     name: "Standard_D2ads_v5",
@@ -743,6 +895,7 @@ const SUPPORTED_AZURE_MACHINE_TYPES: ClientMachineType[] = [
       "uksouth",
       "westus3",
     ],
+    isGPU: false,
   },
   {
     name: "Standard_B4als_v2",
@@ -769,6 +922,7 @@ const SUPPORTED_AZURE_MACHINE_TYPES: ClientMachineType[] = [
       "westus2",
       "westus3",
     ],
+    isGPU: false,
   },
   {
     name: "Standard_NC4as_T4_v3",
@@ -784,6 +938,7 @@ const SUPPORTED_AZURE_MACHINE_TYPES: ClientMachineType[] = [
       "westeurope",
       "westus2",
     ],
+    isGPU: true,
   },
   {
     name: "Standard_NC8as_T4_v3",
@@ -799,6 +954,7 @@ const SUPPORTED_AZURE_MACHINE_TYPES: ClientMachineType[] = [
       "westeurope",
       "westus2",
     ],
+    isGPU: true,
   },
   {
     name: "Standard_NC16as_T4_v3",
@@ -814,6 +970,7 @@ const SUPPORTED_AZURE_MACHINE_TYPES: ClientMachineType[] = [
       "westeurope",
       "westus2",
     ],
+    isGPU: true,
   },
   {
     name: "Standard_NC64as_T4_v3",
@@ -829,6 +986,7 @@ const SUPPORTED_AZURE_MACHINE_TYPES: ClientMachineType[] = [
       "westeurope",
       "westus2",
     ],
+    isGPU: true,
   },
   {
     name: "Standard_D8s_v3",
@@ -849,6 +1007,7 @@ const SUPPORTED_AZURE_MACHINE_TYPES: ClientMachineType[] = [
       "uaenorth",
       "uksouth",
     ],
+    isGPU: false,
   },
 ];
 const SUPPORTED_AZURE_SKU_TIERS = [
@@ -956,10 +1115,10 @@ const AWS_VCPUS_QUOTA_RESOLUTION: PreflightCheckResolution = {
         "https://us-east-1.console.aws.amazon.com/servicequotas/home/services/ec2/quotas",
     },
     {
-      text: 'Search for "Running On-Demand Standard (A, C, D, H, I, M, R, T, Z) instances" in the search box and click on the search result.',
+      text: "Input the reported quota name from the provision check error into the search box and click on the search result.",
     },
     {
-      text: 'Click on "Request quota increase". In order to provision with Porter, you will need to request at least 10 vCPUs above your current quota limit.',
+      text: 'Click on "Request quota increase". In order to provision with Porter, the new quota value must be at least the number reported from the provision check error.',
     },
     {
       text: "Once that request is approved, return to Porter and retry the provision.",

+ 9 - 0
dashboard/src/lib/clusters/types.ts

@@ -163,6 +163,14 @@ const awsMachineTypeValidator = z.enum([
   "c7a.12xlarge",
   "c7a.16xlarge",
   "c7a.24xlarge",
+  "c7g.medium",
+  "c7g.large",
+  "c7g.xlarge",
+  "c7g.2xlarge",
+  "c7g.4xlarge",
+  "c7g.8xlarge",
+  "c7g.12xlarge",
+  "c7g.16xlarge",
   // gpu types
   "g4dn.xlarge",
   "g4dn.2xlarge",
@@ -228,6 +236,7 @@ export type ClientMachineType = {
   name: AWSMachineType | GCPMachineType | AzureMachineType;
   displayName: string;
   supportedRegions: Array<AWSRegion | GCPRegion | AzureRegion>;
+  isGPU: boolean;
 };
 type PreflightCheckResolutionStep = {
   text: string;

+ 1 - 11
dashboard/src/main/home/app-dashboard/create-app/CreateApp.tsx

@@ -56,7 +56,6 @@ import {
   type PopulatedEnvGroup,
 } from "../validate-apply/app-settings/types";
 import ServiceList from "../validate-apply/services-settings/ServiceList";
-import PorterYamlModal from "./PorterYamlModal";
 import RepoSettings from "./RepoSettings";
 
 type CreateAppProps = RouteComponentProps;
@@ -69,10 +68,6 @@ const CreateApp: React.FC<CreateAppProps> = ({ history }) => {
     count: number;
   }>({ detected: false, count: 0 });
   const [showGHAModal, setShowGHAModal] = React.useState(false);
-  const [
-    userHasSeenNoPorterYamlFoundModal,
-    setUserHasSeenNoPorterYamlFoundModal,
-  ] = React.useState(false);
   const isNameValid = (value: string): boolean => {
     return /^[a-z0-9-]{1,63}$/.test(value);
   };
@@ -195,12 +190,7 @@ const CreateApp: React.FC<CreateAppProps> = ({ history }) => {
   const image = watch("source.image");
   const services = watch("app.services");
 
-  const {
-    detectedServices: servicesFromYaml,
-    detectedName,
-    porterYamlFound,
-    loading: isLoadingPorterYaml,
-  } = usePorterYaml({
+  const { detectedServices: servicesFromYaml, detectedName } = usePorterYaml({
     source: source?.type === "github" ? source : null,
     appName: "", // only want to know if porter.yaml has name set, otherwise use name from input
   });

+ 25 - 6
dashboard/src/main/home/infrastructure-dashboard/ClusterStatus.tsx

@@ -1,4 +1,5 @@
 import React, { useMemo } from "react";
+import _ from "lodash";
 import pluralize from "pluralize";
 import styled from "styled-components";
 
@@ -12,8 +13,18 @@ import { useClusterContext } from "./ClusterContextProvider";
 const ClusterStatus: React.FC = () => {
   const { nodes, isClusterUpdating } = useClusterContext();
 
-  const applicationNodes = useMemo(() => {
-    return nodes.filter((n) => n.nodeGroupType === "APPLICATION");
+  const nodeInformation = useMemo(() => {
+    const applicationNodes = nodes.filter(
+      (n) => n.nodeGroupType === "APPLICATION"
+    );
+    const customNodes = nodes.filter((n) => n.nodeGroupType === "CUSTOM");
+    if (!applicationNodes.length) {
+      return;
+    }
+    return {
+      APPLICATION: applicationNodes,
+      CUSTOM: customNodes,
+    };
   }, [nodes]);
 
   return (
@@ -26,14 +37,22 @@ const ClusterStatus: React.FC = () => {
           <Text color="helper">Updating</Text>
         </>
       ) : (
-        applicationNodes.length !== 0 && (
+        nodeInformation && (
           <>
             <StatusDot status={"available"} heightPixels={8} />
             <Spacer inline x={0.7} />
             <Text color="helper">
-              Applications using {applicationNodes.length}{" "}
-              <Code>{applicationNodes[0].instanceType}</Code>{" "}
-              {pluralize("instance", applicationNodes.length)}
+              Applications running on {nodeInformation.APPLICATION.length}{" "}
+              <Code>{nodeInformation.APPLICATION[0].instanceType}</Code>{" "}
+              {pluralize("instance", nodeInformation.APPLICATION.length)}
+              {nodeInformation.CUSTOM.length !== 0 && (
+                <>
+                  {" and "}
+                  {nodeInformation.CUSTOM.length}{" "}
+                  <Code>{nodeInformation.CUSTOM[0].instanceType}</Code>{" "}
+                  {pluralize("instance", nodeInformation.CUSTOM.length)}
+                </>
+              )}
             </Text>
           </>
         )

+ 6 - 8
dashboard/src/main/home/infrastructure-dashboard/modals/PreflightChecksModal.tsx

@@ -64,12 +64,10 @@ export const CheckItem: React.FC<ItemProps> = ({ preflightCheck }) => {
           {preflightCheck.error.metadata &&
             Object.entries(preflightCheck.error.metadata).map(
               ([key, value]) => (
-                <>
-                  <div key={key}>
-                    <ErrorMessageLabel>{key}:</ErrorMessageLabel>
-                    <ErrorMessageContent>{value}</ErrorMessageContent>
-                  </div>
-                </>
+                <div key={key}>
+                  <ErrorMessageLabel>{key}:</ErrorMessageLabel>
+                  <ErrorMessageContent>{value}</ErrorMessageContent>
+                </div>
               )
             )}
         </div>
@@ -107,12 +105,12 @@ const PreflightChecksModal: React.FC<Props> = ({
 
 export default PreflightChecksModal;
 
-const AppearingDiv = styled.div<{ color?: string }>`
+const AppearingDiv = styled.div`
   animation: floatIn 0.5s;
   animation-fill-mode: forwards;
   display: flex;
   flex-direction: column;
-  color: ${(props) => props.color || "#ffffff44"};
+  color: #fff;
 
   @keyframes floatIn {
     from {

+ 5 - 0
dashboard/src/main/home/infrastructure-dashboard/modals/help/preflight/ResolutionStepsModalContents.tsx

@@ -2,6 +2,7 @@ import React from "react";
 import styled from "styled-components";
 
 import Link from "components/porter/Link";
+import ShowIntercomButton from "components/porter/ShowIntercomButton";
 import Spacer from "components/porter/Spacer";
 import Step from "components/porter/Step";
 import Text from "components/porter/Text";
@@ -32,6 +33,10 @@ const ElasticIPQuotaModalContents: React.FC<Props> = ({ resolution }) => {
           </Step>
         ))}
       </StepContainer>
+      <Spacer y={1} />
+      <ShowIntercomButton message="I need help resolving preflight check errors.">
+        Need help? Talk to support
+      </ShowIntercomButton>
     </div>
   );
 };

+ 173 - 20
dashboard/src/main/home/infrastructure-dashboard/shared/NodeGroups.tsx

@@ -1,6 +1,9 @@
-import React, { useMemo } from "react";
+import React, { useContext, useMemo } from "react";
+import _ from "lodash";
 import { Controller, useFieldArray, useFormContext } from "react-hook-form";
+import styled from "styled-components";
 
+import Button from "components/porter/Button";
 import Container from "components/porter/Container";
 import Expandable from "components/porter/Expandable";
 import Image from "components/porter/Image";
@@ -13,6 +16,8 @@ import {
   type ClientMachineType,
 } from "lib/clusters/types";
 
+import { Context } from "shared/Context";
+import chip from "assets/computer-chip.svg";
 import world from "assets/world.svg";
 
 type Props = {
@@ -20,25 +25,32 @@ type Props = {
 };
 const NodeGroups: React.FC<Props> = ({ availableMachineTypes }) => {
   const { control } = useFormContext<ClientClusterContract>();
-  const { fields: nodeGroups } = useFieldArray({
+  const { currentProject } = useContext(Context);
+  const {
+    fields: nodeGroups,
+    append,
+    remove,
+  } = useFieldArray({
     control,
     name: "cluster.config.nodeGroups",
   });
   const displayableNodeGroups = useMemo(() => {
-    const dng = nodeGroups.map((ng, idx) => {
-      return {
-        nodeGroup: ng,
-        idx,
-        isIncluded: ng.nodeGroupType === "APPLICATION",
-      };
-    });
+    const dng = _.groupBy(
+      nodeGroups.map((ng, idx) => {
+        return {
+          nodeGroup: ng,
+          idx,
+        };
+      }),
+      (ng) => ng.nodeGroup.nodeGroupType
+    );
     return dng;
   }, [nodeGroups]);
 
   return (
-    <>
-      {displayableNodeGroups.map((ng) => {
-        return ng.isIncluded ? (
+    <NodeGroupContainer>
+      {displayableNodeGroups.APPLICATION?.map((ng) => {
+        return (
           <Expandable
             preExpanded={true}
             key={ng.nodeGroup.id}
@@ -46,8 +58,7 @@ const NodeGroups: React.FC<Props> = ({ availableMachineTypes }) => {
               <Container row>
                 <Image src={world} />
                 <Spacer inline x={1} />
-                {ng.nodeGroup.nodeGroupType === "APPLICATION" &&
-                  "Default node group"}
+                Default node group
               </Container>
             }
           >
@@ -58,10 +69,12 @@ const NodeGroups: React.FC<Props> = ({ availableMachineTypes }) => {
                 <>
                   <Select
                     width="300px"
-                    options={availableMachineTypes.map((t) => ({
-                      value: t.name,
-                      label: t.displayName,
-                    }))}
+                    options={availableMachineTypes
+                      .filter((t) => !t.isGPU)
+                      .map((t) => ({
+                        value: t.name,
+                        label: t.displayName,
+                      }))}
                     value={value.instanceType}
                     setValue={(newInstanceType: string) => {
                       onChange({
@@ -111,10 +124,150 @@ const NodeGroups: React.FC<Props> = ({ availableMachineTypes }) => {
               )}
             />
           </Expandable>
-        ) : null;
+        );
       })}
-    </>
+      {displayableNodeGroups.CUSTOM?.map((ng) => {
+        return (
+          <Expandable
+            preExpanded={true}
+            key={ng.nodeGroup.id}
+            header={
+              <Container row spaced>
+                <Container row>
+                  <Image src={chip} />
+                  <Spacer inline x={1} />
+                  GPU node group
+                </Container>
+                <Container row>
+                  <ActionButton
+                    onClick={(e) => {
+                      e.stopPropagation();
+                      remove(ng.idx);
+                    }}
+                  >
+                    <span className="material-icons">delete</span>
+                  </ActionButton>
+                </Container>
+              </Container>
+            }
+          >
+            <Controller
+              name={`cluster.config.nodeGroups.${ng.idx}`}
+              control={control}
+              render={({ field: { value, onChange } }) => (
+                <>
+                  <Select
+                    width="300px"
+                    options={availableMachineTypes
+                      .filter((t) => t.isGPU)
+                      .map((t) => ({
+                        value: t.name,
+                        label: t.displayName,
+                      }))}
+                    value={value.instanceType}
+                    setValue={(newInstanceType: string) => {
+                      onChange({
+                        ...value,
+                        instanceType: newInstanceType,
+                      });
+                    }}
+                    label="Machine type"
+                  />
+                  <Spacer y={1} />
+                  <Text color="helper">Minimum number of GPU nodes</Text>
+                  <Spacer y={0.5} />
+                  <Input
+                    width="75px"
+                    type="number"
+                    disabled={false}
+                    value={value.minInstances.toString()}
+                    setValue={(newMinInstances: string) => {
+                      onChange({
+                        ...value,
+                        minInstances: parseInt(newMinInstances),
+                      });
+                    }}
+                    placeholder="ex: 1"
+                  />
+                  <Spacer y={1} />
+                  <Text color="helper">Maximum number of GPU nodes</Text>
+                  <Spacer y={0.5} />
+                  <Input
+                    width="75px"
+                    type="number"
+                    disabled={false}
+                    value={value.maxInstances.toString()}
+                    setValue={(newMaxInstances: string) => {
+                      onChange({
+                        ...value,
+                        maxInstances: parseInt(newMaxInstances),
+                      });
+                    }}
+                    placeholder="ex: 10"
+                  />
+                </>
+              )}
+            />
+          </Expandable>
+        );
+      })}
+      {(displayableNodeGroups.CUSTOM ?? []).length === 0 &&
+        currentProject?.gpu_enabled && (
+          <Button
+            alt
+            onClick={() => {
+              append({
+                nodeGroupType: "CUSTOM",
+                instanceType: "g4dn.xlarge",
+                minInstances: 1,
+                maxInstances: 2,
+              });
+            }}
+          >
+            <I className="material-icons">add</I>
+            Add a GPU node group
+          </Button>
+        )}
+    </NodeGroupContainer>
   );
 };
 
 export default NodeGroups;
+
+const NodeGroupContainer = styled.div`
+  display: flex;
+  flex-direction: column;
+  gap: 10px;
+`;
+
+const I = styled.i`
+  font-size: 20px;
+  cursor: pointer;
+  padding: 5px;
+  color: #aaaabb;
+  :hover {
+    color: white;
+  }
+`;
+
+const ActionButton = styled.button`
+  position: relative;
+  border: none;
+  background: none;
+  color: white;
+  padding: 5px;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  border-radius: 50%;
+  cursor: pointer;
+  color: #aaaabb;
+  :hover {
+    color: white;
+  }
+
+  > span {
+    font-size: 20px;
+  }
+  margin-right: 5px;
+`;

+ 40 - 3
dashboard/src/main/home/sidebar/ClusterList.tsx

@@ -3,6 +3,7 @@ import { withRouter } from "react-router";
 import styled from "styled-components";
 
 import Icon from "components/porter/Icon";
+import { useClusterList } from "lib/hooks/useCluster";
 
 import api from "shared/api";
 import { Context } from "shared/Context";
@@ -30,6 +31,7 @@ const ClusterList: React.FC = (props) => {
   const wrapperRef = useRef<HTMLDivElement>(null);
   const [clusters, setClusters] = useState<ClusterType[]>([]);
   const [options, setOptions] = useState<ClusterOptions[]>([]);
+  const { clusters: clusterList } = useClusterList();
 
   useEffect(() => {
     const handleClickOutside = (e: MouseEvent): void => {
@@ -50,6 +52,19 @@ const ClusterList: React.FC = (props) => {
 
   useEffect(() => {
     if (currentProject) {
+      if (
+        currentProject?.simplified_view_enabled &&
+        currentProject?.capi_provisioner_enabled &&
+        currentProject?.beta_features_enabled
+      ) {
+        setOptions(
+          clusterList.map((c) => ({
+            label: c.vanity_name,
+            value: c.name,
+          }))
+        );
+        return;
+      }
       api
         .getClusters("<token>", {}, { id: currentProject?.id })
         .then((res) => {
@@ -90,9 +105,31 @@ const ClusterList: React.FC = (props) => {
         title={option.label}
         onClick={() => {
           setExpanded(false);
-          const cluster = clusters.find((c) => c.name === option.value);
-          setCurrentCluster(cluster);
-          pushFiltered(props, "/apps", ["project_id"], {});
+          if (
+            currentProject?.simplified_view_enabled &&
+            currentProject?.capi_provisioner_enabled &&
+            currentProject?.beta_features_enabled
+          ) {
+            const cluster = clusterList.find((c) => c.name === option.value);
+            if (cluster) {
+              // TODO: remove the need for this conversion
+              const clusterToOldType: ClusterType = {
+                id: cluster.id,
+                name: cluster.name,
+                vanity_name: cluster.vanity_name,
+                cloud_provider: cluster.cloud_provider.name,
+                status: cluster.status,
+              };
+              setCurrentCluster?.(clusterToOldType);
+              pushFiltered(props, "/apps", ["project_id"], {});
+            }
+          } else {
+            const cluster = clusters.find((c) => c.name === option.value);
+            if (cluster) {
+              setCurrentCluster?.(cluster);
+              pushFiltered(props, "/apps", ["project_id"], {});
+            }
+          }
         }}
       >
         <Icon src={infra} height={"14px"} />

+ 1 - 1
dashboard/src/shared/types.tsx

@@ -22,7 +22,7 @@ export type ClusterType = {
   id: number;
   name: string;
   vanity_name?: string;
-  server: string;
+  server?: string;
   service_account_id?: number;
   agent_integration_enabled?: boolean;
   infra_id?: number;