Explorar el Código

committing work

Feroze Mohideen hace 3 años
padre
commit
4f31cda7ed

+ 6 - 2
Tiltfile

@@ -12,10 +12,14 @@ if config.tilt_subcommand == "down":
     local(command="rm -rf vendor")
     local(command="rm -rf dashboard/node_modules")
 
+build_args = "GOOS=linux GOARCH=arm64"
+if os.getenv("PLATFORM") == "amd64":
+    build_args = "GOOS=linux GOARCH=amd64"
+
 ## Build binary locally for faster devexp
 local_resource(
   name='porter-binary',
-  cmd='''GOWORK=off CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -mod vendor -gcflags '-N -l' -tags ee -o ./bin/porter ./cmd/app/main.go''',
+  cmd='''GOWORK=off CGO_ENABLED=0 %s go build -mod vendor -gcflags '-N -l' -tags ee -o ./bin/porter ./cmd/app/main.go''' % build_args,
   deps=[
     "api",
     "build",
@@ -62,7 +66,7 @@ local_resource(
 # Migrations
 local_resource(
     name="migrations-binary",
-    cmd='''GOWORK=off CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -mod vendor -gcflags '-N -l' -tags ee -o ./bin/migrate ./cmd/migrate/main.go ./cmd/migrate/migrate_ee.go''',
+    cmd='''GOWORK=off CGO_ENABLED=0 %s go build -mod vendor -gcflags '-N -l' -tags ee -o ./bin/migrate ./cmd/migrate/main.go ./cmd/migrate/migrate_ee.go''' % build_args,
     resource_deps=["postgresql"],
     labels=["porter"],
 )

+ 11 - 11
api/server/handlers/project_integration/preflight_check_aws.go → api/server/handlers/project_integration/preflight_check_aws_role.go

@@ -14,21 +14,21 @@ import (
 	"github.com/porter-dev/porter/internal/models"
 )
 
-type CreatePreflightCheckAWSHandler struct {
+type CreatePreflightCheckAWSRoleHandler struct {
 	handlers.PorterHandlerReadWriter
 }
 
-func NewCreatePreflightCheckAWSHandler(
+func NewCreatePreflightCheckAWSRoleHandler(
 	config *config.Config,
 	decoderValidator shared.RequestDecoderValidator,
 	writer shared.ResultWriter,
-) *CreatePreflightCheckAWSHandler {
-	return &CreatePreflightCheckAWSHandler{
+) *CreatePreflightCheckAWSRoleHandler {
+	return &CreatePreflightCheckAWSRoleHandler{
 		PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
 	}
 }
 
-func (p *CreatePreflightCheckAWSHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+func (p *CreatePreflightCheckAWSRoleHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	project, _ := r.Context().Value(types.ProjectScope).(*models.Project)
 	ctx := r.Context()
 
@@ -38,13 +38,13 @@ func (p *CreatePreflightCheckAWSHandler) ServeHTTP(w http.ResponseWriter, r *htt
 	}
 
 	res := types.RolePreflightCheckResponse{
-		TargetARN: 	     request.TargetARN,
+		TargetARN: request.TargetARN,
 	}
 
 	checkReq := porterv1.RolePreflightCheckRequest{
-		ProjectId:       int64(project.ID),
-		TargetArn:		 request.TargetARN,
-		ExternalId: 	 request.ExternalID,
+		ProjectId:  int64(project.ID),
+		TargetArn:  request.TargetARN,
+		ExternalId: request.ExternalID,
 	}
 
 	checkResp, err := p.Config().ClusterControlPlaneClient.RolePreflightCheck(ctx, connect.NewRequest(&checkReq))
@@ -54,8 +54,8 @@ func (p *CreatePreflightCheckAWSHandler) ServeHTTP(w http.ResponseWriter, r *htt
 		p.HandleAPIError(w, r, apierrors.NewErrInternal(e))
 		return
 	}
-	
+
 	res.TargetARN = checkResp.Msg.TargetArn
 
 	p.WriteResult(w, r, checkResp)
-}
+}

+ 55 - 0
api/server/handlers/project_integration/preflight_check_aws_usage.go

@@ -0,0 +1,55 @@
+package project_integration
+
+import (
+	"fmt"
+	"net/http"
+
+	"github.com/bufbuild/connect-go"
+	porterv1 "github.com/porter-dev/api-contracts/generated/go/porter/v1"
+	"github.com/porter-dev/porter/api/server/handlers"
+	"github.com/porter-dev/porter/api/server/shared"
+	"github.com/porter-dev/porter/api/server/shared/apierrors"
+	"github.com/porter-dev/porter/api/server/shared/config"
+	"github.com/porter-dev/porter/api/types"
+	"github.com/porter-dev/porter/internal/models"
+)
+
+type CreatePreflightCheckAWSUsageHandler struct {
+	handlers.PorterHandlerReadWriter
+}
+
+func NewCreatePreflightCheckAWSUsageHandler(
+	config *config.Config,
+	decoderValidator shared.RequestDecoderValidator,
+	writer shared.ResultWriter,
+) *CreatePreflightCheckAWSUsageHandler {
+	return &CreatePreflightCheckAWSUsageHandler{
+		PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
+	}
+}
+
+func (p *CreatePreflightCheckAWSUsageHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	project, _ := r.Context().Value(types.ProjectScope).(*models.Project)
+	ctx := r.Context()
+
+	request := &types.QuotaPreflightCheckRequest{}
+	if ok := p.DecodeAndValidate(w, r, request); !ok {
+		return
+	}
+
+	checkReq := porterv1.QuotaPreflightCheckRequest{
+		ProjectId: int64(project.ID),
+		TargetArn: request.TargetARN,
+		Region:    request.Region,
+	}
+
+	checkResp, err := p.Config().ClusterControlPlaneClient.QuotaPreflightCheck(ctx, connect.NewRequest(&checkReq))
+
+	if err != nil {
+		e := fmt.Errorf("preflight check failed: %w", err)
+		p.HandleAPIError(w, r, apierrors.NewErrInternal(e))
+		return
+	}
+
+	p.WriteResult(w, r, checkResp)
+}

+ 36 - 16
api/server/handlers/user/create_test.go

@@ -1,7 +1,6 @@
 package user_test
 
 import (
-	"fmt"
 	"net/http"
 	"testing"
 
@@ -18,8 +17,11 @@ func TestCreateUserSuccessful(t *testing.T) {
 		string(types.HTTPVerbPost),
 		"/api/users",
 		&types.CreateUserRequest{
-			Email:    "test@test.it",
-			Password: "somepassword",
+			FirstName:   "Mister",
+			LastName:    "Porter",
+			CompanyName: "Porter Technologies, Inc.",
+			Email:       "mrp@porter.run",
+			Password:    "somepassword",
 		},
 	)
 
@@ -35,7 +37,10 @@ func TestCreateUserSuccessful(t *testing.T) {
 
 	expUser := &types.CreateUserResponse{
 		ID:            1,
-		Email:         "test@test.it",
+		FirstName:     "Mister",
+		LastName:      "Porter",
+		CompanyName:   "Porter Technologies, Inc.",
+		Email:         "mrp@porter.run",
 		EmailVerified: false,
 	}
 
@@ -50,8 +55,11 @@ func TestCreateUserBadEmail(t *testing.T) {
 		string(types.HTTPVerbPost),
 		"/api/users",
 		&types.CreateUserRequest{
-			Email:    "notanemail",
-			Password: "somepassword",
+			FirstName:   "Mister",
+			LastName:    "Porter",
+			CompanyName: "Porter Technologies, Inc.",
+			Email:       "notanemail",
+			Password:    "somepassword",
 		},
 	)
 
@@ -66,7 +74,7 @@ func TestCreateUserBadEmail(t *testing.T) {
 	handler.ServeHTTP(rr, req)
 
 	apitest.AssertResponseError(t, rr, http.StatusBadRequest, &types.ExternalError{
-		Error: fmt.Sprintf("validation failed on field 'Email' on condition 'email'"),
+		Error: "validation failed on field 'Email' on condition 'email'",
 	})
 }
 
@@ -76,7 +84,10 @@ func TestCreateUserMissingField(t *testing.T) {
 		string(types.HTTPVerbPost),
 		"/api/users",
 		&types.CreateUserRequest{
-			Email: "test@test.it",
+			FirstName:   "Mister",
+			LastName:    "Porter",
+			CompanyName: "Porter Technologies, Inc.",
+			Email:       "mrp@porter.run",
 		},
 	)
 
@@ -91,7 +102,7 @@ func TestCreateUserMissingField(t *testing.T) {
 	handler.ServeHTTP(rr, req)
 
 	apitest.AssertResponseError(t, rr, http.StatusBadRequest, &types.ExternalError{
-		Error: fmt.Sprintf("validation failed on field 'Password' on condition 'required'"),
+		Error: "validation failed on field 'Password' on condition 'required'",
 	})
 }
 
@@ -101,8 +112,11 @@ func TestCreateUserSameEmail(t *testing.T) {
 		string(types.HTTPVerbPost),
 		"/api/users",
 		&types.CreateUserRequest{
-			Email:    "test@test.it",
-			Password: "somepassword",
+			FirstName:   "Mister",
+			LastName:    "Porter",
+			CompanyName: "Porter Technologies, Inc.",
+			Email:       "mrp@porter.run",
+			Password:    "somepassword",
 		},
 	)
 
@@ -120,7 +134,7 @@ func TestCreateUserSameEmail(t *testing.T) {
 	handler.ServeHTTP(rr, req)
 
 	apitest.AssertResponseError(t, rr, http.StatusBadRequest, &types.ExternalError{
-		Error: fmt.Sprintf("email already taken"),
+		Error: "email already taken",
 	})
 }
 
@@ -130,8 +144,11 @@ func TestFailingCreateUserMethod(t *testing.T) {
 		string(types.HTTPVerbPost),
 		"/api/users",
 		&types.CreateUserRequest{
-			Email:    "test@test.it",
-			Password: "somepassword",
+			FirstName:   "Mister",
+			LastName:    "Porter",
+			CompanyName: "Porter Technologies, Inc.",
+			Email:       "mrp@porter.run",
+			Password:    "somepassword",
 		},
 	)
 
@@ -154,8 +171,11 @@ func TestFailingCreateSessionMethod(t *testing.T) {
 		string(types.HTTPVerbPost),
 		"/api/users",
 		&types.CreateUserRequest{
-			Email:    "test@test.it",
-			Password: "somepassword",
+			FirstName:   "Mister",
+			LastName:    "Porter",
+			CompanyName: "Porter Technologies, Inc.",
+			Email:       "mrp@porter.run",
+			Password:    "somepassword",
 		},
 	)
 

+ 4 - 1
api/server/handlers/user/current_test.go

@@ -25,7 +25,10 @@ func TestGetCurrentUserSuccessful(t *testing.T) {
 
 	expUser := &types.GetAuthenticatedUserResponse{
 		ID:            1,
-		Email:         "test@test.it",
+		FirstName:     "Mister",
+		LastName:      "Porter",
+		CompanyName:   "Porter Technologies, Inc.",
+		Email:         "mrp@porter.run",
 		EmailVerified: true,
 	}
 

+ 4 - 1
api/server/handlers/user/delete_test.go

@@ -33,7 +33,10 @@ func TestDeleteUserSuccessful(t *testing.T) {
 
 	expUser := &types.CreateUserResponse{
 		ID:            1,
-		Email:         "test@test.it",
+		FirstName:     "Mister",
+		LastName:      "Porter",
+		CompanyName:   "Porter Technologies, Inc.",
+		Email:         "mrp@porter.run",
 		EmailVerified: true,
 	}
 

+ 1 - 1
api/server/handlers/user/email_verify_test.go

@@ -33,7 +33,7 @@ func TestEmailVerifyInitiateSuccessful(t *testing.T) {
 	}
 
 	initiateOpts := fakeNotifier.GetSendEmailVerificationLastOpts()
-	assert.Equal(t, "test@test.it", initiateOpts.Email)
+	assert.Equal(t, "mrp@porter.run", initiateOpts.Email)
 
 	// parse the url and compare
 	parsedURL, err := url.Parse(initiateOpts.URL)

+ 8 - 5
api/server/handlers/user/login_test.go

@@ -18,7 +18,7 @@ func TestLoginUserSuccessful(t *testing.T) {
 		string(types.HTTPVerbPost),
 		"/api/login",
 		&types.LoginUserRequest{
-			Email:    "test@test.it",
+			Email:    "mrp@porter.run",
 			Password: "hello",
 		},
 	)
@@ -36,7 +36,10 @@ func TestLoginUserSuccessful(t *testing.T) {
 
 	expUser := &types.LoginUserResponse{
 		ID:            1,
-		Email:         "test@test.it",
+		FirstName:     "Mister",
+		LastName:      "Porter",
+		CompanyName:   "Porter Technologies, Inc.",
+		Email:         "mrp@porter.run",
 		EmailVerified: true,
 	}
 
@@ -51,7 +54,7 @@ func TestLoginUserIncorrectPassword(t *testing.T) {
 		string(types.HTTPVerbPost),
 		"/api/login",
 		&types.LoginUserRequest{
-			Email:    "test@test.it",
+			Email:    "mrp@porter.run",
 			Password: "hello1",
 		},
 	)
@@ -105,7 +108,7 @@ func TestLoginUserEmptyPassword(t *testing.T) {
 		string(types.HTTPVerbPost),
 		"/api/login",
 		&types.LoginUserRequest{
-			Email:    "test@test.it",
+			Email:    "mrp@porter.run",
 			Password: "",
 		},
 	)
@@ -157,7 +160,7 @@ func TestLoginUserFailingReadUserByEmailMethod(t *testing.T) {
 		string(types.HTTPVerbPost),
 		"/api/login",
 		&types.LoginUserRequest{
-			Email:    "test@test.it",
+			Email:    "mrp@porter.run",
 			Password: "hello",
 		},
 	)

+ 34 - 6
api/server/router/project_integration.go

@@ -220,14 +220,14 @@ func getProjectIntegrationRoutes(
 		Router:   r,
 	})
 
-	// POST /api/projects/{project_id}/integrations/aws/preflightcheck -> project_integration.NewCreatePreflightCheckAWSHandler
-	preflightCheckAWSEndpoint := factory.NewAPIEndpoint(
+	// POST /api/projects/{project_id}/integrations/aws/preflightcheck/role -> project_integration.NewCreatePreflightCheckAWSHandler
+	preflightCheckAWSRoleEndpoint := factory.NewAPIEndpoint(
 		&types.APIRequestMetadata{
 			Verb:   types.APIVerbCreate,
 			Method: types.HTTPVerbPost,
 			Path: &types.Path{
 				Parent:       basePath,
-				RelativePath: relPath + "/aws/preflight",
+				RelativePath: relPath + "/aws/preflight/role",
 			},
 			Scopes: []types.PermissionScope{
 				types.UserScope,
@@ -236,15 +236,43 @@ func getProjectIntegrationRoutes(
 		},
 	)
 
-	preflightCheckAWSHandler := project_integration.NewCreatePreflightCheckAWSHandler(
+	preflightCheckAWSRoleHandler := project_integration.NewCreatePreflightCheckAWSRoleHandler(
 		config,
 		factory.GetDecoderValidator(),
 		factory.GetResultWriter(),
 	)
 
 	routes = append(routes, &router.Route{
-		Endpoint: preflightCheckAWSEndpoint,
-		Handler:  preflightCheckAWSHandler,
+		Endpoint: preflightCheckAWSRoleEndpoint,
+		Handler:  preflightCheckAWSRoleHandler,
+		Router:   r,
+	})
+
+	// POST /api/projects/{project_id}/integrations/aws/preflightcheck/usage -> project_integration.NewCreatePreflightCheckAWSHandler
+	preflightCheckAWSUsageEndpoint := factory.NewAPIEndpoint(
+		&types.APIRequestMetadata{
+			Verb:   types.APIVerbCreate,
+			Method: types.HTTPVerbPost,
+			Path: &types.Path{
+				Parent:       basePath,
+				RelativePath: relPath + "/aws/preflight/usage",
+			},
+			Scopes: []types.PermissionScope{
+				types.UserScope,
+				types.ProjectScope,
+			},
+		},
+	)
+
+	preflightCheckAWSUsageHandler := project_integration.NewCreatePreflightCheckAWSUsageHandler(
+		config,
+		factory.GetDecoderValidator(),
+		factory.GetResultWriter(),
+	)
+
+	routes = append(routes, &router.Route{
+		Endpoint: preflightCheckAWSUsageEndpoint,
+		Handler:  preflightCheckAWSUsageHandler,
 		Router:   r,
 	})
 

+ 4 - 1
api/server/shared/apitest/user.go

@@ -12,7 +12,10 @@ func CreateTestUser(t *testing.T, config *config.Config, verified bool) *models.
 	hashedPw, _ := bcrypt.GenerateFromPassword([]byte("hello"), 8)
 
 	user, err := config.Repo.User().CreateUser(&models.User{
-		Email:         "test@test.it",
+		FirstName:     "Mister",
+		LastName:      "Porter",
+		CompanyName:   "Porter Technologies, Inc.",
+		Email:         "mrp@porter.run",
 		Password:      string(hashedPw),
 		EmailVerified: verified,
 	})

+ 13 - 4
api/types/project_integration.go

@@ -76,15 +76,24 @@ type AWSIntegration struct {
 type ListAWSResponse []*AWSIntegration
 
 type RolePreflightCheckRequest struct {
-	ProjectID	  uint	 `json:"project_id"`
-	TargetARN     string `json:"target_arn"`
-	ExternalID	  string `json:"external_id"`
+	ProjectID  uint   `json:"project_id"`
+	TargetARN  string `json:"target_arn"`
+	ExternalID string `json:"external_id"`
 }
 
 type RolePreflightCheckResponse struct {
-	TargetARN     string `json:"target_arn"`
+	TargetARN string `json:"target_arn"`
 }
 
+type QuotaPreflightCheckRequest struct {
+	ProjectID  uint   `json:"project_id"`
+	TargetARN  string `json:"target_arn"`
+	ExternalID string `json:"external_id"`
+	Region     string `json:"region"`
+}
+
+type QuotaPreflightCheckResponse struct{}
+
 type CreateAWSRequest struct {
 	AWSRegion          string `json:"aws_region"`
 	AWSClusterID       string `json:"aws_cluster_id"`

+ 2 - 0
dashboard/package-lock.json

@@ -12077,6 +12077,7 @@
       "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz",
       "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==",
       "dev": true,
+      "hasInstallScript": true,
       "optional": true,
       "os": [
         "darwin"
@@ -12621,6 +12622,7 @@
       "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz",
       "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==",
       "dev": true,
+      "hasInstallScript": true,
       "optional": true,
       "os": [
         "darwin"

+ 5 - 2
dashboard/src/components/CloudFormationForm.tsx

@@ -18,9 +18,9 @@ import DocsHelper from "./DocsHelper";
 
 type Props = {
   goBack: () => void;
-  proceed: () => void;
   AWSAccountID: string;
   setAWSAccountID: (id: string) => void;
+  proceed: (id: string) => void;
 };
 
 const CloudFormationForm: React.FC<Props> = ({
@@ -55,7 +55,7 @@ const CloudFormationForm: React.FC<Props> = ({
     //     setCreateStatus("Error creating credentials");
     //   });
     setRoleStatus("successful");
-    proceed();
+    proceed(targetARN);
   };
 
   const directToCloudFormation = () => {
@@ -85,6 +85,9 @@ const CloudFormationForm: React.FC<Props> = ({
                 👤 AWS account ID
                 <i
                   className="material-icons"
+                  onClick={() => {
+                    window.open("https://console.aws.amazon.com/billing/home?region=us-east-1#/account", "_blank")
+                  }}
                 >
                   help_outline
                 </i>

+ 27 - 31
dashboard/src/components/ProvisionerFlow.tsx

@@ -72,45 +72,48 @@ const ProvisionerFlow: React.FC<Props> = ({
             setShowCostConfirmModal(false);
           }}>
             <Text size={16} weight={500}>
-              AWS base cost consent
+              Base AWS cost consent
             </Text>
             <Spacer height="15px" />
             <Text color="helper">
-              Porter will create resources in your existing AWS account for hosting your applications. You will be separately charged by AWS and can use your cloud credits.
-            </Text>
-            <Spacer y={1} />
-            <Text color="helper">
-              AWS base cost before cloud credits:
+              Porter will create resources in your existing AWS account for hosting applications. You will be separately charged by AWS and can use your cloud credits. Base AWS cost:
             </Text>
             <Spacer y={1} />
             <ExpandableSection
-              background="#ffffff11"
+              noWrapper
+              expandText="[+] Show details"
+              collapseText="[-] Hide details"
               Header={
                 <Cost>$315.94 / mo</Cost>
               }
               ExpandedSection={
-                <Dark>
-                  <Text>
-                    • Amazon Elastic Kubernetes Service (EKS) = $73/mo
-                    <Spacer height="15px" />
-                    • Amazon EC2:
-                    <Spacer height="15px" />
-                    <Tab />+ System workloads: t3.medium instance (2) = $60.74/mo
-                    <Spacer height="15px" />
-                    <Tab />+ Monitoring workloads: t3.large instance (1) = $60.74/mo
-                    <Spacer height="15px" />
-                    <Tab />+ Application workloads: t3.xlarge instance (1) = $121.47/mo
-                  </Text>
-                </Dark>
+                <>
+                  <Spacer height="15px" />
+                  <Fieldset background="#1b1d2688">
+                    <Text>
+                      • Amazon Elastic Kubernetes Service (EKS) = $73/mo
+                      <Spacer height="15px" />
+                      • Amazon EC2:
+                      <Spacer height="15px" />
+                      <Tab />+ System workloads: t3.medium instance (2) = $60.74/mo
+                      <Spacer height="15px" />
+                      <Tab />+ Monitoring workloads: t3.large instance (1) = $60.74/mo
+                      <Spacer height="15px" />
+                      <Tab />+ Application workloads: t3.xlarge instance (1) = $121.47/mo
+                    </Text>
+                  </Fieldset>
+                </>
               }
             />
             <Spacer y={1} />
             <Text color="helper">
-              Porter metered cost: $0.019/hr/vCPU + $0.009/hr/GB RAM.
+              Separate from the AWS cost, Porter charges based on the amount of resources that are being used. Porter pricing is as follows, prorated to the minute:
             </Text>
+            <Spacer y={1}/>
+            <Cost>$0.019/hr/vCPU + $0.009/hr/GB RAM</Cost>
             <Spacer y={1} />
             <Text color="helper">
-              All AWS resources will be automatically deleted when you delete your Porter project. Please enter the base cost ("315.94") below to proceed:
+              All AWS resources will be automatically deleted when you delete your Porter project. Please enter the AWS base cost ("315.94") below to proceed:
             </Text>
             <Spacer y={1} />
             <Input placeholder="315.94" value={confirmCost} setValue={setConfirmCost} width="100%" height="40px" />
@@ -133,8 +136,8 @@ const ProvisionerFlow: React.FC<Props> = ({
     return (
       <CloudFormationForm
         goBack={() => setCurrentStep("cloud")}
-        proceed={() => {
-          // setCredentialId(id);
+        proceed={(id) => {
+          setCredentialId(id);
           setCurrentStep("cluster");
         }}
         AWSAccountID={AWSAccountID}
@@ -154,13 +157,6 @@ const ProvisionerFlow: React.FC<Props> = ({
 
 export default ProvisionerFlow;
 
-const Dark = styled.div`
-  position: relative;
-  padding: 25px;
-  background: #1b1d2688;
-  font-size: 13px;
-`;
-
 const Cost = styled.div`
   font-weight: 600;
   font-size: 20px;

+ 52 - 52
dashboard/src/components/ProvisionerSettings.tsx

@@ -123,60 +123,60 @@ const ProvisionerSettings: React.FC<Props> = props => {
       data["cluster"]["clusterId"] = props.clusterId;
     }
 
-    api
-      .preflightCheckAWSUsage(
-        "<token>",
-        {
-          // TODO: change this back
-          // target_arn: `arn:aws:iam::${props.AWSAccountID}:role/porter-role`,
-          target_arn: "arn:aws:iam::844966915049:role/PorterRole-RootRole-1S8V1TYOMOXTK",
-          region: awsRegion
-        },
-        {
-          id: currentProject.id,
-        }
-      )
-      .then(({ data }) => {
-        console.log(data)
-      })
-      .catch((err) => {
-        console.error(err);
-      });
-
-    // try {
-    //   const res = await api.createContract(
+    // api
+    //   .preflightCheckAWSUsage(
     //     "<token>",
-    //     data,
-    //     { project_id: currentProject.id }
-    //   );
+    //     {
+    //       // TODO: change this back
+    //       // target_arn: `arn:aws:iam::${props.AWSAccountID}:role/porter-role`,
+    //       target_arn: "arn:aws:iam::844966915049:role/PorterRole-RootRole-1S8V1TYOMOXTK",
+    //       region: awsRegion
+    //     },
+    //     {
+    //       id: currentProject.id,
+    //     }
+    //   )
+    //   .then(({ data }) => {
+    //     console.log(data)
+    //   })
+    //   .catch((err) => {
+    //     console.error(err);
+    //   });
+
+    try {
+      const res = await api.createContract(
+        "<token>",
+        data,
+        { project_id: currentProject.id }
+      );
 
-    //   // Only refresh and set clusters on initial create
-    //   if (!props.clusterId) {
-    //     setShouldRefreshClusters(true);
-    //     api.getClusters(
-    //       "<token>",
-    //       {},
-    //       { id: currentProject.id },
-    //     )
-    //       .then(({ data }) => {
-    //         data.forEach((cluster: ClusterType) => {
-    //           if (cluster.id === res.data.contract_revision?.cluster_id) {
-    //             // setHasFinishedOnboarding(true);
-    //             setCurrentCluster(cluster);
-    //             OFState.actions.goTo("clean_up");
-    //             pushFiltered(props, "/cluster-dashboard", ["project_id"], {
-    //               cluster: cluster.name,
-    //             });
-    //           }
-    //         });
-    //       })
-    //       .catch((err) => {
-    //         console.error(err);
-    //       });
-    //   }
-    // } catch (err) {
-    //   console.log(err);
-    // }
+      // Only refresh and set clusters on initial create
+      if (!props.clusterId) {
+        setShouldRefreshClusters(true);
+        api.getClusters(
+          "<token>",
+          {},
+          { id: currentProject.id },
+        )
+          .then(({ data }) => {
+            data.forEach((cluster: ClusterType) => {
+              if (cluster.id === res.data.contract_revision?.cluster_id) {
+                // setHasFinishedOnboarding(true);
+                setCurrentCluster(cluster);
+                OFState.actions.goTo("clean_up");
+                pushFiltered(props, "/cluster-dashboard", ["project_id"], {
+                  cluster: cluster.name,
+                });
+              }
+            });
+          })
+          .catch((err) => {
+            console.error(err);
+          });
+      }
+    } catch (err) {
+      console.log(err);
+    }
   }
 
   useEffect(() => {

+ 41 - 14
dashboard/src/components/porter/ExpandableSection.tsx

@@ -1,5 +1,6 @@
 import React, { useEffect, useState } from "react";
 import styled from "styled-components";
+import Container from "./Container";
 
 type Props = {
   isInitiallyExpanded?: boolean;
@@ -7,6 +8,10 @@ type Props = {
   ExpandedSection: any;
   color?: any;
   background?: string;
+  noWrapper?: boolean;
+  expandText?: string;
+  collapseText?: string;
+  maxHeight?: string;
 };
 
 const ExpandableSection: React.FC<Props> = ({
@@ -15,6 +20,10 @@ const ExpandableSection: React.FC<Props> = ({
   ExpandedSection,
   color,
   background,
+  noWrapper,
+  expandText,
+  collapseText,
+  maxHeight,
 }) => {
   const [isExpanded, setIsExpanded] = useState(false);
   useEffect(() => {
@@ -25,15 +34,25 @@ const ExpandableSection: React.FC<Props> = ({
     <StyledExpandableSection 
       isExpanded={isExpanded}
       background={background}
+      noWrapper={noWrapper}
     >
-      <HeaderRow 
-        isExpanded={isExpanded}
-        onClick={() => setIsExpanded(!isExpanded)}
-        color={color}
-      >
-        <i className="material-icons">arrow_drop_down</i> 
-        {Header}
-      </HeaderRow>
+      {noWrapper ? (
+        <Container row>
+          {Header}
+          <ExpandButton onClick={() => setIsExpanded(!isExpanded)}>
+            {isExpanded ? collapseText : expandText}
+          </ExpandButton>
+        </Container>
+      ) : (
+        <HeaderRow 
+          isExpanded={isExpanded}
+          onClick={() => setIsExpanded(!isExpanded)}
+          color={color}
+        >
+          {!noWrapper && <i className="material-icons">arrow_drop_down</i>}
+          {Header}
+        </HeaderRow>
+      )}
       {
         isExpanded && (
           ExpandedSection
@@ -45,6 +64,13 @@ const ExpandableSection: React.FC<Props> = ({
 
 export default ExpandableSection;
 
+const ExpandButton = styled.div`
+  margin-left: 15px;
+  color: #aaaabb;
+  cursor: pointer;
+  font-size: 13px;
+`;
+
 const HeaderRow = styled.div<{ 
   isExpanded: boolean;
   color?: string;
@@ -74,16 +100,17 @@ const HeaderRow = styled.div<{
 const StyledExpandableSection = styled.div<{ 
   isExpanded: boolean;
   background?: string;
+  noWrapper?: boolean;
 }>`
   width: 100%;
-  height: ${props => props.isExpanded ? "" : "40px"};
-  max-height: 255px;
+  height: ${props => (props.isExpanded || props.noWrapper) ? "" : "40px"};
+  max-height: 300px;
   overflow: hidden;
   border-radius: 5px;
-  background: ${props => props.background || "#26292e"};
-  border: 1px solid #494b4f;
+  background: ${props => !props.noWrapper && (props.background || "#26292e")};
+  border: ${props => !props.noWrapper && "1px solid #494b4f"};
   :hover {
-    border: 1px solid #7a7b80;
+    border: ${props => !props.noWrapper && "1px solid #7a7b80"};
   }
   animation: ${props => props.isExpanded ? "expandRevisions 0.3s" : ""};
   animation-timing-function: ease-out;
@@ -92,7 +119,7 @@ const StyledExpandableSection = styled.div<{
       max-height: 40px;
     }
     to {
-      max-height: 250px;
+      max-height: 300px;
     }
   }
 `;

+ 3 - 1
dashboard/src/components/porter/Spacer.tsx

@@ -3,6 +3,7 @@ import styled from "styled-components";
 
 type Props = {
   height?: string;
+  width?: string;
   y?: number;
   x?: number;
   inline?: boolean;
@@ -10,6 +11,7 @@ type Props = {
 
 const Spacer: React.FC<Props> = ({
   height,
+  width,
   y,
   x,
   inline,
@@ -31,7 +33,7 @@ const Spacer: React.FC<Props> = ({
   return (
     <StyledSpacer
       height={height || getCalcHeight()}
-      width={inline && getCalcWidth()}
+      width={inline && (width || getCalcWidth())}
     />
   );
 };

+ 1 - 1
dashboard/src/main/auth/Login.tsx

@@ -226,7 +226,7 @@ const Login: React.FC<Props> = ({
           size={13}
           color="helper"
         >
-          Don't have an account? <Link to="/register">Sign up</Link>
+          Don't have an account?<Spacer width="5px" inline /><Link to="/register">Sign up</Link>
         </Text>
       </Wrapper>
     </StyledLogin>

+ 1 - 1
dashboard/src/main/auth/Register.tsx

@@ -292,7 +292,7 @@ const Register: React.FC<Props> = ({
           size={13}
           color="helper"
         >
-          Already have an account? <Link to="/login">Log in</Link>
+          Already have an account?<Spacer width="5px" inline /><Link to="/login">Log in</Link>
         </Text>
       </Wrapper>
     </StyledRegister>

+ 3 - 2
dashboard/src/main/home/modals/Modal.tsx

@@ -146,8 +146,9 @@ const StyledModal = styled.div<{
   z-index: 999;
   font-size: 13px;
   border-radius: 10px;
-  background: #202227;
-  border: 1px solid #ffffff55;
+  background: #42444944;
+  backdrop-filter: saturate(150%) blur(10px);
+  border: 1px solid #494b4f;
   overflow: auto;
   color: #ffffff;
   animation: floatInModal 0.5s 0s;

+ 1 - 1
go.mod

@@ -74,7 +74,7 @@ require (
 	github.com/glebarez/sqlite v1.6.0
 	github.com/nats-io/nats.go v1.24.0
 	github.com/open-policy-agent/opa v0.44.0
-	github.com/porter-dev/api-contracts v0.0.48
+	github.com/porter-dev/api-contracts v0.0.51
 	github.com/santhosh-tekuri/jsonschema/v5 v5.0.1
 	github.com/stefanmcshane/helm v0.0.0-20221213002717-88a4a2c6e77d
 	github.com/xanzy/go-gitlab v0.68.0

+ 2 - 0
go.sum

@@ -1468,6 +1468,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
 github.com/polyfloyd/go-errorlint v0.0.0-20210722154253-910bb7978349/go.mod h1:wi9BfjxjF/bwiZ701TzmfKu6UKC357IOAtNr0Td0Lvw=
 github.com/porter-dev/api-contracts v0.0.48 h1:i6B6H26+am/H61YC1u9u7gAah+opnWS7qfUxZQrkWjo=
 github.com/porter-dev/api-contracts v0.0.48/go.mod h1:qr2L58mJLr5DUGV5OPw3REiSrQvJq6TgkKyEWP95dyU=
+github.com/porter-dev/api-contracts v0.0.51 h1:gQIHqG78DuvjxQ44epFwwO69+nO3/LaZDqhl//60xNg=
+github.com/porter-dev/api-contracts v0.0.51/go.mod h1:qr2L58mJLr5DUGV5OPw3REiSrQvJq6TgkKyEWP95dyU=
 github.com/porter-dev/switchboard v0.0.0-20221019155755-67ff2bf04935 h1:hfb3nt3AJXIBbevu6ARTg9SdOkMP6WLbKBiG5hT5rcc=
 github.com/porter-dev/switchboard v0.0.0-20221019155755-67ff2bf04935/go.mod h1:xSPzqSFMQ6OSbp42fhCi4AbGbQbsm6nRvOkrblFeXU4=
 github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=

+ 5 - 0
go.work.sum

@@ -1,15 +1,20 @@
 cloud.google.com/go/compute v1.12.1/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU=
 cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
 github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60=
+github.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y=
 github.com/containerd/stargz-snapshotter v0.11.3 h1:D3PoF563XmOBdtfx2G6AkhbHueqwIVPBFn2mrsWLa3w=
+github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y=
 github.com/emicklei/go-restful v2.9.5+incompatible h1:spTtZBk5DYEvbxMVutUuTyh1Ao2r4iyvLdACqsl/Ljk=
+github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
 github.com/go-redis/redis v6.15.8+incompatible h1:BKZuG6mCnRj5AOaWJXoCgf6rqTYnYJLe4en2hxT7r9o=
 github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0=
 github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A=
+github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f h1:2+myh5ml7lgEU/51gbeLHfKGNfgEQQIWrlbdaOsidbQ=
 github.com/nats-io/nats.go v1.9.1 h1:ik3HbLhZ0YABLto7iX80pZLPw/6dx3T+++MZJwLnMrQ=
 github.com/nats-io/nkeys v0.1.0 h1:qMd4+pRHgdr1nAClu+2h/2a5F2TmKcCzjCDazVgRoX4=
 github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g=
+github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ=
 github.com/tchap/go-patricia v2.2.6+incompatible h1:JvoDL7JSoIP2HDE8AbDH3zC8QBPxmzYe32HHy5yQ+Ck=
 golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
 golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=