Преглед изворни кода

Merge branch 'master' of github.com:porter-dev/porter into simplified-view

Feroze Mohideen пре 3 година
родитељ
комит
0db33aff2d

+ 5 - 6
.github/workflows/prerelease.yaml

@@ -15,7 +15,7 @@ jobs:
       - name: Checkout
       - name: Checkout
         uses: actions/checkout@v3
         uses: actions/checkout@v3
       - name: Setup docker
       - name: Setup docker
-        uses: docker/login-action@v1
+        uses: docker/login-action@v2
         with:
         with:
           username: ${{ secrets.DOCKERHUB_USERNAME }}
           username: ${{ secrets.DOCKERHUB_USERNAME }}
           password: ${{ secrets.DOCKERHUB_TOKEN }}
           password: ${{ secrets.DOCKERHUB_TOKEN }}
@@ -234,7 +234,7 @@ jobs:
           unzip ./release/UNSIGNED_portersvr_${{steps.tag_name.outputs.tag}}_Darwin_x86_64.zip
           unzip ./release/UNSIGNED_portersvr_${{steps.tag_name.outputs.tag}}_Darwin_x86_64.zip
           unzip ./release/UNSIGNED_docker-credential-porter_${{steps.tag_name.outputs.tag}}_Darwin_x86_64.zip
           unzip ./release/UNSIGNED_docker-credential-porter_${{steps.tag_name.outputs.tag}}_Darwin_x86_64.zip
       - name: Import Code-Signing Certificates
       - name: Import Code-Signing Certificates
-        uses: Apple-Actions/import-codesign-certs@v1
+        uses: Apple-Actions/import-codesign-certs@v2
         with:
         with:
           # The certificates in a PKCS12 file encoded as a base64 string
           # The certificates in a PKCS12 file encoded as a base64 string
           p12-file-base64: ${{ secrets.APPLE_DEVELOPER_CERTIFICATE_P12_BASE64 }}
           p12-file-base64: ${{ secrets.APPLE_DEVELOPER_CERTIFICATE_P12_BASE64 }}
@@ -339,12 +339,11 @@ jobs:
           path: release/darwin
           path: release/darwin
       - name: Create Release
       - name: Create Release
         id: create_release
         id: create_release
-        uses: actions/create-release@v1
-        env:
-          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        uses: softprops/action-gh-release@v1
         with:
         with:
           tag_name: ${{ github.ref }}
           tag_name: ${{ github.ref }}
-          release_name: Release ${{ github.ref }}
+          name: Release ${{ github.ref }}
+          token: ${{ secrets.GITHUB_TOKEN }}
           draft: false
           draft: false
           prerelease: true
           prerelease: true
       - name: Upload Linux CLI Release Asset
       - name: Upload Linux CLI Release Asset

+ 1 - 1
.github/workflows/release.yaml

@@ -24,7 +24,7 @@ jobs:
         env:
         env:
           GITHUB_TAG: ${{ github.ref }}
           GITHUB_TAG: ${{ github.ref }}
       - name: Setup docker
       - name: Setup docker
-        uses: docker/login-action@v1
+        uses: docker/login-action@v2
         with:
         with:
           username: ${{ secrets.DOCKERHUB_USERNAME }}
           username: ${{ secrets.DOCKERHUB_USERNAME }}
           password: ${{ secrets.DOCKERHUB_TOKEN }}
           password: ${{ secrets.DOCKERHUB_TOKEN }}

+ 1 - 1
api/server/shared/apierrors/alerter/alerter.go

@@ -5,5 +5,5 @@ import (
 )
 )
 
 
 type Alerter interface {
 type Alerter interface {
-	SendAlert(ctx context.Context, err error, data map[string]interface{})
+	SendAlert(ctx context.Context, err error, data map[string]interface{}) *string
 }
 }

+ 3 - 1
api/server/shared/apierrors/alerter/noop.go

@@ -6,4 +6,6 @@ import (
 
 
 type NoOpAlerter struct{}
 type NoOpAlerter struct{}
 
 
-func (s NoOpAlerter) SendAlert(ctx context.Context, err error, data map[string]interface{}) {}
+func (s NoOpAlerter) SendAlert(ctx context.Context, err error, data map[string]interface{}) *string {
+	return nil
+}

+ 9 - 2
api/server/shared/apierrors/alerter/sentry.go

@@ -31,18 +31,25 @@ func NewSentryAlerter(sentryDSN, sentryEnv string) (*SentryAlerter, error) {
 	}, nil
 	}, nil
 }
 }
 
 
-func (s *SentryAlerter) SendAlert(ctx context.Context, err error, data map[string]interface{}) {
+func (s *SentryAlerter) SendAlert(ctx context.Context, err error, data map[string]interface{}) *string {
 	scope := sentry.NewScope()
 	scope := sentry.NewScope()
 
 
 	for key, val := range data {
 	for key, val := range data {
 		scope.SetTag(key, fmt.Sprintf("%v", val))
 		scope.SetTag(key, fmt.Sprintf("%v", val))
 	}
 	}
 
 
-	s.client.CaptureException(
+	eventID := s.client.CaptureException(
 		err,
 		err,
 		&sentry.EventHint{
 		&sentry.EventHint{
 			Data: data,
 			Data: data,
 		},
 		},
 		scope,
 		scope,
 	)
 	)
+
+	if eventID != nil {
+		eventIDStr := string(*eventID)
+		return &eventIDStr
+	}
+
+	return nil
 }
 }

+ 7 - 5
api/server/shared/apierrors/errors.go

@@ -123,7 +123,7 @@ type ErrorOpts struct {
 
 
 func HandleAPIError(
 func HandleAPIError(
 	l *logger.Logger,
 	l *logger.Logger,
-	alerter alerter.Alerter,
+	al alerter.Alerter,
 	w http.ResponseWriter,
 	w http.ResponseWriter,
 	r *http.Request,
 	r *http.Request,
 	err RequestError,
 	err RequestError,
@@ -143,11 +143,15 @@ func HandleAPIError(
 	event.Send()
 	event.Send()
 
 
 	// if the status code is internal server error, use alerter
 	// if the status code is internal server error, use alerter
-	if err.GetStatusCode() == http.StatusInternalServerError && alerter != nil {
+	if err.GetStatusCode() == http.StatusInternalServerError && al != nil {
 		data["method"] = r.Method
 		data["method"] = r.Method
 		data["url"] = r.URL.String()
 		data["url"] = r.URL.String()
 
 
-		alerter.SendAlert(r.Context(), err, data)
+		eventID := al.SendAlert(r.Context(), err, data)
+
+		if _, ok := al.(*alerter.SentryAlerter); ok && eventID != nil {
+			data["sentry_event_id"] = *eventID
+		}
 	}
 	}
 
 
 	if writeErr {
 	if writeErr {
@@ -175,6 +179,4 @@ func HandleAPIError(
 			event.Send()
 			event.Send()
 		}
 		}
 	}
 	}
-
-	return
 }
 }

+ 36 - 38
api/server/shared/config/loader/loader.go

@@ -78,15 +78,18 @@ func (e *EnvConfigLoader) LoadConfig() (res *config.Config, err error) {
 		BillingManager:    InstanceBillingManager,
 		BillingManager:    InstanceBillingManager,
 		CredentialBackend: InstanceCredentialBackend,
 		CredentialBackend: InstanceCredentialBackend,
 	}
 	}
-
+	res.Logger.Info().Msg("Loading MetadataFromConf")
 	res.Metadata = config.MetadataFromConf(envConf.ServerConf, e.version)
 	res.Metadata = config.MetadataFromConf(envConf.ServerConf, e.version)
+	res.Logger.Info().Msg("Loaded MetadataFromConf")
 	res.DB = InstanceDB
 	res.DB = InstanceDB
 
 
-	err = gorm.AutoMigrate(InstanceDB, sc.Debug)
+	// res.Logger.Info().Msg("Starting gorm automigrate")
+	// err = gorm.AutoMigrate(InstanceDB, sc.Debug)
 
 
-	if err != nil {
-		return nil, err
-	}
+	// if err != nil {
+	// 	return nil, err
+	// }
+	// res.Logger.Info().Msg("Completed gorm automigrate")
 
 
 	var key [32]byte
 	var key [32]byte
 
 
@@ -94,8 +97,11 @@ func (e *EnvConfigLoader) LoadConfig() (res *config.Config, err error) {
 		key[i] = b
 		key[i] = b
 	}
 	}
 
 
+	res.Logger.Info().Msg("Creating new gorm repository")
 	res.Repo = gorm.NewRepository(InstanceDB, &key, InstanceCredentialBackend)
 	res.Repo = gorm.NewRepository(InstanceDB, &key, InstanceCredentialBackend)
+	res.Logger.Info().Msg("Created new gorm repository")
 
 
+	res.Logger.Info().Msg("Creating new session store")
 	// create the session store
 	// create the session store
 	res.Store, err = sessionstore.NewStore(
 	res.Store, err = sessionstore.NewStore(
 		&sessionstore.NewStoreOpts{
 		&sessionstore.NewStoreOpts{
@@ -108,6 +114,7 @@ func (e *EnvConfigLoader) LoadConfig() (res *config.Config, err error) {
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
+	res.Logger.Info().Msg("Created new session store")
 
 
 	res.TokenConf = &token.TokenGeneratorConf{
 	res.TokenConf = &token.TokenGeneratorConf{
 		TokenSecret: envConf.ServerConf.TokenGeneratorSecret,
 		TokenSecret: envConf.ServerConf.TokenGeneratorSecret,
@@ -116,6 +123,7 @@ func (e *EnvConfigLoader) LoadConfig() (res *config.Config, err error) {
 	res.UserNotifier = &notifier.EmptyUserNotifier{}
 	res.UserNotifier = &notifier.EmptyUserNotifier{}
 
 
 	if res.Metadata.Email {
 	if res.Metadata.Email {
+		res.Logger.Info().Msg("Creating new user notifier")
 		res.UserNotifier = sendgrid.NewUserNotifier(&sendgrid.UserNotifierOpts{
 		res.UserNotifier = sendgrid.NewUserNotifier(&sendgrid.UserNotifierOpts{
 			SharedOpts: &sendgrid.SharedOpts{
 			SharedOpts: &sendgrid.SharedOpts{
 				APIKey:      envConf.ServerConf.SendgridAPIKey,
 				APIKey:      envConf.ServerConf.SendgridAPIKey,
@@ -126,24 +134,33 @@ func (e *EnvConfigLoader) LoadConfig() (res *config.Config, err error) {
 			VerifyEmailTemplateID:   envConf.ServerConf.SendgridVerifyEmailTemplateID,
 			VerifyEmailTemplateID:   envConf.ServerConf.SendgridVerifyEmailTemplateID,
 			ProjectInviteTemplateID: envConf.ServerConf.SendgridProjectInviteTemplateID,
 			ProjectInviteTemplateID: envConf.ServerConf.SendgridProjectInviteTemplateID,
 		})
 		})
+		res.Logger.Info().Msg("Created new user notifier")
 	}
 	}
 
 
 	res.Alerter = alerter.NoOpAlerter{}
 	res.Alerter = alerter.NoOpAlerter{}
 
 
 	if envConf.ServerConf.SentryDSN != "" {
 	if envConf.ServerConf.SentryDSN != "" {
+		res.Logger.Info().Msg("Creating Sentry Alerter")
 		res.Alerter, err = alerter.NewSentryAlerter(envConf.ServerConf.SentryDSN, envConf.ServerConf.SentryEnv)
 		res.Alerter, err = alerter.NewSentryAlerter(envConf.ServerConf.SentryDSN, envConf.ServerConf.SentryEnv)
+		if err != nil {
+			return nil, fmt.Errorf("failed to create new sentry alerter: %w", err)
+		}
+		res.Logger.Info().Msg("Created Sentry Alerter")
 	}
 	}
 
 
 	if sc.DOClientID != "" && sc.DOClientSecret != "" {
 	if sc.DOClientID != "" && sc.DOClientSecret != "" {
+		res.Logger.Info().Msg("Creating Digital Ocean client")
 		res.DOConf = oauth.NewDigitalOceanClient(&oauth.Config{
 		res.DOConf = oauth.NewDigitalOceanClient(&oauth.Config{
 			ClientID:     sc.DOClientID,
 			ClientID:     sc.DOClientID,
 			ClientSecret: sc.DOClientSecret,
 			ClientSecret: sc.DOClientSecret,
 			Scopes:       []string{"read", "write"},
 			Scopes:       []string{"read", "write"},
 			BaseURL:      sc.ServerURL,
 			BaseURL:      sc.ServerURL,
 		})
 		})
+		res.Logger.Info().Msg("Created Digital Ocean client")
 	}
 	}
 
 
 	if sc.GoogleClientID != "" && sc.GoogleClientSecret != "" {
 	if sc.GoogleClientID != "" && sc.GoogleClientSecret != "" {
+		res.Logger.Info().Msg("Creating Google client")
 		res.GoogleConf = oauth.NewGoogleClient(&oauth.Config{
 		res.GoogleConf = oauth.NewGoogleClient(&oauth.Config{
 			ClientID:     sc.GoogleClientID,
 			ClientID:     sc.GoogleClientID,
 			ClientSecret: sc.GoogleClientSecret,
 			ClientSecret: sc.GoogleClientSecret,
@@ -154,16 +171,19 @@ func (e *EnvConfigLoader) LoadConfig() (res *config.Config, err error) {
 			},
 			},
 			BaseURL: sc.ServerURL,
 			BaseURL: sc.ServerURL,
 		})
 		})
+		res.Logger.Info().Msg(" Google client")
 	}
 	}
 
 
 	// TODO: remove this as part of POR-1055
 	// TODO: remove this as part of POR-1055
 	if sc.GithubClientID != "" && sc.GithubClientSecret != "" {
 	if sc.GithubClientID != "" && sc.GithubClientSecret != "" {
+		res.Logger.Info().Msg("Creating Github client")
 		res.GithubConf = oauth.NewGithubClient(&oauth.Config{
 		res.GithubConf = oauth.NewGithubClient(&oauth.Config{
 			ClientID:     sc.GithubClientID,
 			ClientID:     sc.GithubClientID,
 			ClientSecret: sc.GithubClientSecret,
 			ClientSecret: sc.GithubClientSecret,
 			Scopes:       []string{"read:user", "user:email"},
 			Scopes:       []string{"read:user", "user:email"},
 			BaseURL:      sc.ServerURL,
 			BaseURL:      sc.ServerURL,
 		})
 		})
+		res.Logger.Info().Msg("Created Github client")
 	}
 	}
 
 
 	if sc.GithubAppSecretBase64 != "" {
 	if sc.GithubAppSecretBase64 != "" {
@@ -214,6 +234,7 @@ func (e *EnvConfigLoader) LoadConfig() (res *config.Config, err error) {
 	}
 	}
 
 
 	if sc.SlackClientID != "" && sc.SlackClientSecret != "" {
 	if sc.SlackClientID != "" && sc.SlackClientSecret != "" {
+		res.Logger.Info().Msg("Creating Slack client")
 		res.SlackConf = oauth.NewSlackClient(&oauth.Config{
 		res.SlackConf = oauth.NewSlackClient(&oauth.Config{
 			ClientID:     sc.SlackClientID,
 			ClientID:     sc.SlackClientID,
 			ClientSecret: sc.SlackClientSecret,
 			ClientSecret: sc.SlackClientSecret,
@@ -224,6 +245,7 @@ func (e *EnvConfigLoader) LoadConfig() (res *config.Config, err error) {
 			BaseURL: sc.ServerURL,
 			BaseURL: sc.ServerURL,
 		})
 		})
 	}
 	}
+	res.Logger.Info().Msg("Created Slack client")
 
 
 	res.WSUpgrader = &websocket.Upgrader{
 	res.WSUpgrader = &websocket.Upgrader{
 		WSUpgrader: &gorillaws.Upgrader{
 		WSUpgrader: &gorillaws.Upgrader{
@@ -237,37 +259,7 @@ func (e *EnvConfigLoader) LoadConfig() (res *config.Config, err error) {
 						res.Logger.Info().Msgf("error: %s, host: %s, origin: %s, serverURL: %s", err.Error(), r.Host, r.Header.Get("Origin"), sc.ServerURL)
 						res.Logger.Info().Msgf("error: %s, host: %s, origin: %s, serverURL: %s", err.Error(), r.Host, r.Header.Get("Origin"), sc.ServerURL)
 					}
 					}
 				}()
 				}()
-				origin := r.Header.Get("Origin")
-
-				// // check if the server url is localhost, and allow all localhost origins
-				// serverParsed, err := url.Parse(sc.ServerURL)
-				// if err != nil {
-				// 	return false
-				// }
-				// host, _, err := net.SplitHostPort(serverParsed.Host)
-				// if err != nil {
-				// 	return false
-				// }
-				// if host == "localhost" {
-				// 	parsedOrigin, err := url.Parse(origin)
-				// 	if err != nil {
-				// 		return false
-				// 	}
-				// 	originHost, _, err := net.SplitHostPort(parsedOrigin.Host)
-				// 	if err != nil {
-				// 		if !strings.Contains(err.Error(), "missing port in address") {
-				// 			return false
-				// 		}
-				// 		if strings.Contains(parsedOrigin.Host, "ngrok.io") {
-				// 			return true
-				// 		}
-				// 	}
-				// 	if originHost == "localhost" {
-				// 		return true
-				// 	}
-				// }
-
-				return origin == sc.ServerURL
+				return true
 			},
 			},
 		},
 		},
 	}
 	}
@@ -280,16 +272,20 @@ func (e *EnvConfigLoader) LoadConfig() (res *config.Config, err error) {
 	}
 	}
 
 
 	res.WhitelistedUsers = wlUsers
 	res.WhitelistedUsers = wlUsers
-
+	res.Logger.Info().Msg("Creating URL Cache")
 	res.URLCache = urlcache.Init(sc.DefaultApplicationHelmRepoURL, sc.DefaultAddonHelmRepoURL)
 	res.URLCache = urlcache.Init(sc.DefaultApplicationHelmRepoURL, sc.DefaultAddonHelmRepoURL)
+	res.Logger.Info().Msg("Created URL Cache")
 
 
+	res.Logger.Info().Msg("Creating provisioner service client")
 	provClient, err := getProvisionerServiceClient(sc)
 	provClient, err := getProvisionerServiceClient(sc)
-
 	if err == nil && provClient != nil {
 	if err == nil && provClient != nil {
 		res.ProvisionerClient = provClient
 		res.ProvisionerClient = provClient
 	}
 	}
+	res.Logger.Info().Msg("Created provisioner service client")
 
 
+	res.Logger.Info().Msg("Creating analytics client")
 	res.AnalyticsClient = analytics.InitializeAnalyticsSegmentClient(sc.SegmentClientKey, res.Logger)
 	res.AnalyticsClient = analytics.InitializeAnalyticsSegmentClient(sc.SegmentClientKey, res.Logger)
+	res.Logger.Info().Msg("Created analytics client")
 
 
 	if sc.PowerDNSAPIKey != "" && sc.PowerDNSAPIServerURL != "" {
 	if sc.PowerDNSAPIKey != "" && sc.PowerDNSAPIServerURL != "" {
 		res.PowerDNSClient = powerdns.NewClient(sc.PowerDNSAPIServerURL, sc.PowerDNSAPIKey, sc.AppRootDomain)
 		res.PowerDNSClient = powerdns.NewClient(sc.PowerDNSAPIServerURL, sc.PowerDNSAPIKey, sc.AppRootDomain)
@@ -297,11 +293,13 @@ func (e *EnvConfigLoader) LoadConfig() (res *config.Config, err error) {
 
 
 	res.EnableCAPIProvisioner = sc.EnableCAPIProvisioner
 	res.EnableCAPIProvisioner = sc.EnableCAPIProvisioner
 	if sc.EnableCAPIProvisioner {
 	if sc.EnableCAPIProvisioner {
+		res.Logger.Info().Msg("Creating CCP client")
 		if sc.ClusterControlPlaneAddress == "" {
 		if sc.ClusterControlPlaneAddress == "" {
 			return res, errors.New("must provide CLUSTER_CONTROL_PLANE_ADDRESS")
 			return res, errors.New("must provide CLUSTER_CONTROL_PLANE_ADDRESS")
 		}
 		}
 		client := porterv1connect.NewClusterControlPlaneServiceClient(http.DefaultClient, sc.ClusterControlPlaneAddress)
 		client := porterv1connect.NewClusterControlPlaneServiceClient(http.DefaultClient, sc.ClusterControlPlaneAddress)
 		res.ClusterControlPlaneClient = client
 		res.ClusterControlPlaneClient = client
+		res.Logger.Info().Msg("Created CCP client")
 	}
 	}
 
 
 	return res, nil
 	return res, nil

+ 4 - 1
cmd/app/main.go

@@ -38,13 +38,16 @@ func main() {
 		log.Fatal("Config loading failed: ", err)
 		log.Fatal("Config loading failed: ", err)
 	}
 	}
 
 
+	config.Logger.Info().Msg("Initializing data")
 	err = initData(config)
 	err = initData(config)
-
 	if err != nil {
 	if err != nil {
 		log.Fatal("Data initialization failed: ", err)
 		log.Fatal("Data initialization failed: ", err)
 	}
 	}
+	config.Logger.Info().Msg("Initialed data")
 
 
+	config.Logger.Info().Msg("Creating API router")
 	appRouter := router.NewAPIRouter(config)
 	appRouter := router.NewAPIRouter(config)
+	config.Logger.Info().Msg("Created API router")
 
 
 	address := fmt.Sprintf(":%d", config.ServerConf.Port)
 	address := fmt.Sprintf(":%d", config.ServerConf.Port)
 
 

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

@@ -76,9 +76,10 @@ const CloudFormationForm: React.FC<Props> = ({
 
 
   const directToCloudFormation = () => {
   const directToCloudFormation = () => {
     let externalId = getExternalId();
     let externalId = getExternalId();
+    let trustArn = process.env.TRUST_ARN ? process.env.TRUST_ARN : "arn:aws:iam::108458755588:role/CAPIManagement";
     window.open(
     window.open(
       `https://console.aws.amazon.com/cloudformation/home?
       `https://console.aws.amazon.com/cloudformation/home?
-      #/stacks/create/review?templateURL=https://porter-role.s3.us-east-2.amazonaws.com/cloudformation-policy.json&stackName=PorterRole&param_ExternalIdParameter=${externalId}`
+      #/stacks/create/review?templateURL=https://porter-role.s3.us-east-2.amazonaws.com/cloudformation-policy.json&stackName=PorterRole&param_ExternalIdParameter=${externalId}&param_TrustArnParameter=${trustArn}`
     )
     )
   }
   }
 
 

+ 8 - 1
dashboard/src/components/repo-selector/BranchList.tsx

@@ -20,6 +20,13 @@ const BranchList: React.FC<Props> = ({
   actionConfig,
   actionConfig,
   currentBranch,
   currentBranch,
 }) => {
 }) => {
+  const sortBranches = (branches: string[]) => {
+    if (!currentBranch) return branches;
+    return [
+      currentBranch,
+      ...branches.filter((branch) => branch !== currentBranch),
+    ];
+  };
   const [loading, setLoading] = useState(true);
   const [loading, setLoading] = useState(true);
   const [error, setError] = useState(false);
   const [error, setError] = useState(false);
   const [branches, setBranches] = useState<string[]>([]);
   const [branches, setBranches] = useState<string[]>([]);
@@ -99,7 +106,7 @@ const BranchList: React.FC<Props> = ({
               .toLowerCase()
               .toLowerCase()
               .includes(searchFilter.toLowerCase() || "");
               .includes(searchFilter.toLowerCase() || "");
           })
           })
-        : branches.slice(0, 10);
+        : sortBranches(branches).slice(0, 10);
 
 
     if (results.length == 0) {
     if (results.length == 0) {
       return <LoadingWrapper>No matching Branches found.</LoadingWrapper>;
       return <LoadingWrapper>No matching Branches found.</LoadingWrapper>;

+ 138 - 31
dashboard/src/main/home/cluster-dashboard/expanded-chart/build-settings/BuildSettingsTab.tsx

@@ -18,6 +18,7 @@ import BranchList from "components/repo-selector/BranchList";
 import Banner from "components/Banner";
 import Banner from "components/Banner";
 import { UpdateBuildconfigResponse } from "./types";
 import { UpdateBuildconfigResponse } from "./types";
 import BuildpackConfigSection from "./_BuildpackConfigSection";
 import BuildpackConfigSection from "./_BuildpackConfigSection";
+import InputRow from "components/form-components/InputRow";
 
 
 type Props = {
 type Props = {
   chart: ChartTypeWithExtendedConfig;
   chart: ChartTypeWithExtendedConfig;
@@ -49,6 +50,12 @@ const BuildSettingsTab: React.FC<Props> = ({
   const [currentBranch, setCurrentBranch] = useState(
   const [currentBranch, setCurrentBranch] = useState(
     () => chart?.git_action_config?.git_branch
     () => chart?.git_action_config?.git_branch
   );
   );
+  const [gitHubSettingsExpanded, setGitHubSettingsExpanded] = useState(true);
+  const [envVariablesExpanded, setEnvVariablesExpanded] = useState(false);
+  const [branchSelectionExpanded, setBranchSelectionExpanded] = useState(false);
+  const [buildpackSettingsExpanded, setBuildpackSettingsExpanded] = useState(
+    false
+  );
 
 
   const buildpackConfigRef = useRef<{
   const buildpackConfigRef = useRef<{
     isLoading: boolean;
     isLoading: boolean;
@@ -329,40 +336,126 @@ const BuildSettingsTab: React.FC<Props> = ({
           </AlertCardAction>
           </AlertCardAction>
         </AlertCard>
         </AlertCard>
       ) : null} */}
       ) : null} */}
-        <Heading isAtTop>Build environment variables</Heading>
-        <KeyValueArray
-          values={envVariables}
-          envLoader
-          externalValues={{
-            namespace: chart.namespace,
-            clusterId: currentCluster.id,
-          }}
-          setValues={(values) => {
-            setEnvVariables(values);
-          }}
-        ></KeyValueArray>
-
-        <Heading>Select default branch</Heading>
-        <Helper>
-          Change the default branch the deployments will be made from.
-        </Helper>
-        <Banner>
-          You must also update the deploy branch in your GitHub Action file.
-        </Banner>
-        <BranchList
-          actionConfig={currentActionConfig}
-          setBranch={setCurrentBranch}
-          currentBranch={currentBranch}
-        />
+        <Heading isAtTop>
+          <ExpandHeader
+            onClick={() => setGitHubSettingsExpanded(!gitHubSettingsExpanded)}
+            isExpanded={!gitHubSettingsExpanded}
+          >
+            Github Settings
+            <i className="material-icons">arrow_drop_down</i>
+          </ExpandHeader>
+        </Heading>
+        {gitHubSettingsExpanded && (
+          <div>
+            <InputRow
+              disabled={true}
+              label="Git repository"
+              type="text"
+              width="100%"
+              value={chart.git_action_config?.git_repo}
+            />
+            <InputRow
+              disabled={true}
+              label="Branch"
+              type="text"
+              width="100%"
+              value={currentBranch}
+            />
+            {chart.git_action_config.dockerfile_path && (
+              <InputRow
+                disabled={true}
+                label="Dockerfile path"
+                type="text"
+                width="100%"
+                value={chart.git_action_config.dockerfile_path}
+              />
+            )}
+            {!chart.git_action_config.dockerfile_path && (
+              <InputRow
+                disabled={true}
+                label="Dockerfile path"
+                type="text"
+                width="100%"
+                value={chart.git_action_config.folder_path}
+              />
+            )}
+          </div>
+        )}
+        <Heading>
+          <ExpandHeader
+            onClick={() => setEnvVariablesExpanded(!envVariablesExpanded)}
+            isExpanded={!envVariablesExpanded}
+          >
+            Build environment variables
+            <i className="material-icons">arrow_drop_down</i>
+          </ExpandHeader>
+        </Heading>
+
+        {envVariablesExpanded && (
+          <div>
+            <KeyValueArray
+              values={envVariables}
+              envLoader
+              externalValues={{
+                namespace: chart.namespace,
+                clusterId: currentCluster.id,
+              }}
+              setValues={(values) => {
+                setEnvVariables(values);
+              }}
+            ></KeyValueArray>
+          </div>
+        )}
+
+        <Heading>
+          <ExpandHeader
+            onClick={() => setBranchSelectionExpanded(!branchSelectionExpanded)}
+            isExpanded={!branchSelectionExpanded}
+          >
+            Select default branch
+            <i className="material-icons">arrow_drop_down</i>
+          </ExpandHeader>
+        </Heading>
+        {branchSelectionExpanded && (
+          <div>
+            {/* Select default branch content */}
+            <Helper>
+              Change the default branch the deployments will be made from.
+            </Helper>
+            <Banner>
+              You must also update the deploy branch in your GitHub Action file.
+            </Banner>
+            <BranchList
+              actionConfig={currentActionConfig}
+              setBranch={setCurrentBranch}
+              currentBranch={currentBranch}
+            />
+          </div>
+        )}
 
 
         {!chart.git_action_config.dockerfile_path ? (
         {!chart.git_action_config.dockerfile_path ? (
           <>
           <>
-            <Heading>Buildpack settings</Heading>
-            <BuildpackConfigSection
-              ref={buildpackConfigRef}
-              currentChart={chart}
-              actionConfig={currentActionConfig}
-            />
+            <Heading>
+              <ExpandHeader
+                onClick={() =>
+                  setBuildpackSettingsExpanded(!buildpackSettingsExpanded)
+                }
+                isExpanded={!buildpackSettingsExpanded}
+              >
+                Buildpacks settings
+                <i className="material-icons">arrow_drop_down</i>
+              </ExpandHeader>
+            </Heading>
+            {buildpackSettingsExpanded &&
+              !chart.git_action_config.dockerfile_path && (
+                <div>
+                  <BuildpackConfigSection
+                    ref={buildpackConfigRef}
+                    currentChart={chart}
+                    actionConfig={currentActionConfig}
+                  />
+                </div>
+              )}
           </>
           </>
         ) : null}
         ) : null}
         <SaveButtonWrapper>
         <SaveButtonWrapper>
@@ -485,3 +578,17 @@ const AlertCardAction = styled.button`
     cursor: pointer;
     cursor: pointer;
   }
   }
 `;
 `;
+
+const ExpandHeader = styled.div<{ isExpanded: boolean }>`
+  display: flex;
+  align-items: center;
+  cursor: pointer;
+  > i {
+    margin-left: 10px;
+    transform: ${(props) => (props.isExpanded ? "rotate(180deg)" : "")};
+  }
+`;
+const DarkMatter = styled.div`
+  width: 100%;
+  margin-bottom: -28px;
+`;

+ 62 - 58
dashboard/src/main/home/cluster-dashboard/expanded-chart/build-settings/_BuildpackConfigSection.tsx

@@ -134,67 +134,71 @@ const BuildpackConfigSection = forwardRef<
 
 
     detectBuildpack()
     detectBuildpack()
       .then(({ data }) => {
       .then(({ data }) => {
-        const builders = data;
-
-        const defaultBuilder = builders.find((builder) =>
-          builder.builders.find((stack) => stack === currentBuildConfig.builder)
-        );
+        {
+          console.log(data);
+          const builders = data;
 
 
-        const nonSelectedBuilder = builders.find(
-          (builder) =>
-            !builder.builders.find(
+          const defaultBuilder = builders.find((builder) =>
+            builder.builders.find(
               (stack) => stack === currentBuildConfig.builder
               (stack) => stack === currentBuildConfig.builder
             )
             )
-        );
-
-        const fullDetectedBuildpacks = [
-          ...defaultBuilder.detected,
-          ...defaultBuilder.others,
-        ];
-
-        const userSelectedBuildpacks = populateBuildpacks(
-          currentBuildConfig.buildpacks,
-          fullDetectedBuildpacks
-        ).filter((b) => b.buildpack);
-
-        const availableBuildpacks = differenceBy(
-          fullDetectedBuildpacks,
-          userSelectedBuildpacks,
-          "buildpack"
-        );
-
-        const defaultStack = defaultBuilder.builders.find((stack) => {
-          return stack === currentBuildConfig.builder;
-        });
-
-        populateState(
-          defaultBuilder.name.toLowerCase(),
-          defaultStack,
-          userSelectedBuildpacks,
-          availableBuildpacks
-        );
-
-        populateState(
-          nonSelectedBuilder.name.toLowerCase(),
-          nonSelectedBuilder.builders[0],
-          nonSelectedBuilder.others,
-          nonSelectedBuilder.detected
-        );
-
-        setBuilders(builders);
-        setSelectedBuilder(defaultBuilder.name.toLowerCase());
-
-        setStacks(defaultBuilder.builders);
-        setSelectedStack(defaultStack);
-        if (!Array.isArray(userSelectedBuildpacks)) {
-          setSelectedBuildpacks([]);
-        } else {
-          setSelectedBuildpacks(userSelectedBuildpacks);
-        }
-        if (!Array.isArray(availableBuildpacks)) {
-          setAvailableBuildpacks([]);
-        } else {
-          setAvailableBuildpacks(availableBuildpacks);
+          );
+
+          const nonSelectedBuilder = builders.find(
+            (builder) =>
+              !builder.builders.find(
+                (stack) => stack === currentBuildConfig.builder
+              )
+          );
+
+          const fullDetectedBuildpacks = [
+            ...(defaultBuilder.detected ?? []),
+            ...(defaultBuilder.others ?? []),
+          ];
+          const userSelectedBuildpacks = populateBuildpacks(
+            currentBuildConfig.buildpacks,
+            fullDetectedBuildpacks
+          ).filter((b) => b.buildpack);
+
+          const availableBuildpacks = differenceBy(
+            fullDetectedBuildpacks,
+            userSelectedBuildpacks,
+            "buildpack"
+          );
+
+          const defaultStack = defaultBuilder.builders.find((stack) => {
+            return stack === currentBuildConfig.builder;
+          });
+
+          populateState(
+            defaultBuilder.name.toLowerCase(),
+            defaultStack,
+            userSelectedBuildpacks,
+            availableBuildpacks
+          );
+
+          populateState(
+            nonSelectedBuilder.name.toLowerCase(),
+            nonSelectedBuilder.builders[0],
+            nonSelectedBuilder.others,
+            nonSelectedBuilder.detected
+          );
+
+          setBuilders(builders);
+          setSelectedBuilder(defaultBuilder.name.toLowerCase());
+
+          setStacks(defaultBuilder.builders);
+          setSelectedStack(defaultStack);
+          if (!Array.isArray(userSelectedBuildpacks)) {
+            setSelectedBuildpacks([]);
+          } else {
+            setSelectedBuildpacks(userSelectedBuildpacks);
+          }
+          if (!Array.isArray(availableBuildpacks)) {
+            setAvailableBuildpacks([]);
+          } else {
+            setAvailableBuildpacks(availableBuildpacks);
+          }
         }
         }
       })
       })
       .catch((err) => {
       .catch((err) => {

+ 1 - 4
dashboard/src/main/home/cluster-dashboard/preview-environments/deployments/DeploymentList.tsx

@@ -311,7 +311,7 @@ const DeploymentList = () => {
       {porterYAMLErrors.length > 0 ? (
       {porterYAMLErrors.length > 0 ? (
         <PorterYAMLBannerWrapper>
         <PorterYAMLBannerWrapper>
           <Banner type="warning">
           <Banner type="warning">
-            We found some errors in the porter.yaml file in the default branch.
+            No porter.yaml file in the default branch.
             <LinkButton
             <LinkButton
               onClick={() => {
               onClick={() => {
                 setExpandedPorterYAMLErrors(porterYAMLErrors);
                 setExpandedPorterYAMLErrors(porterYAMLErrors);
@@ -363,9 +363,6 @@ const DeploymentList = () => {
             name="Sort"
             name="Sort"
           />
           />
           <CreatePreviewEnvironmentButton
           <CreatePreviewEnvironmentButton
-            disabled={porterYAMLErrors.some(
-              (err) => err === PorterYAMLErrors.FileNotFound
-            )}
             to={`/preview-environments/deployments/${environment_id}/${repo_owner}/${repo_name}/create`}
             to={`/preview-environments/deployments/${environment_id}/${repo_owner}/${repo_name}/create`}
           >
           >
             <i className="material-icons">add</i> New preview deployment
             <i className="material-icons">add</i> New preview deployment

+ 18 - 2
dashboard/src/main/home/modals/UpgradeChartModal.tsx

@@ -71,7 +71,15 @@ ${note.note}
 
 
         this.setState({ notes: noteArr.join("\n") });
         this.setState({ notes: noteArr.join("\n") });
       })
       })
-      .catch((err) => console.log(err));
+      .catch((err) => {
+        console.log(err);
+        this.setState({
+          notes: `
+      ## Version ${this.props.currentChart.chart.metadata.version} -> ${this.props.currentChart.latest_version}
+      No upgrade notes available. This update should be backwards-compatible.
+        `,
+        });
+      });
   }
   }
 
 
   renderContent() {
   renderContent() {
@@ -79,7 +87,7 @@ ${note.note}
       return <Loading />;
       return <Loading />;
     }
     }
 
 
-    return <Markdown>{this.state.notes}</Markdown>;
+    return <StyledContent>{this.state.notes}</StyledContent>;
   }
   }
 
 
   render() {
   render() {
@@ -150,3 +158,11 @@ const StyledUpgradeChartModal = styled.div`
   line-height: 1.8em;
   line-height: 1.8em;
   font-family: Work Sans, sans-serif;
   font-family: Work Sans, sans-serif;
 `;
 `;
+const StyledContent = styled.div`
+  /* Add your custom styles for the content here */
+  margin-top: 16px;
+  font-size: 16px;
+  line-height: 1.5;
+  color: #ffffff;
+  overflow: auto;
+`;