Repository: replicatedhq/kots
Pull request #1147 (2020-10-01 19:38:10)

Version card/version history redesign
<img width="1587" alt="Screen Shot 2020-10-01 at 3 50 21 PM" src="https://user-images.githubusercontent.com/25138806/94871051-ed6ec180-03fd-11eb-981c-2080231bf70c.png">
<img width="1067" alt="Screen Shot 2020-10-01 at 3 48 58 PM" src="https://user-images.githubusercontent.com/25138806/94871055-ef388500-03fd-11eb-863e-d2b694b6948d.png">
<img width="1061" alt="Screen Shot 2020-10-01 at 3 49 45 PM" src="https://user-images.githubusercontent.com/25138806/94871059-f1024880-03fd-11eb-9163-aa56ad98193d.png">
<img width="1074" alt="Screen Shot 2020-10-02 at 8 55 44 AM" src="https://user-images.githubusercontent.com/25138806/94943778-1722f980-048d-11eb-9476-0f0a44129907.png">
<img width="361" alt="Screen Shot 2020-10-02 at 8 35 15 AM" src="https://user-images.githubusercontent.com/25138806/94941872-6156ab80-048a-11eb-817b-6dec358ef0d5.png">


CLAassistant: [![CLA assistant check](https://cla-assistant.io/pull/badge/not_signed)](https://cla-assistant.io/replicatedhq/kots?pullRequest=1147) <br/>Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our [Contributor License Agreement](https://cla-assistant.io/replicatedhq/kots?pullRequest=1147) before we can accept your contribution.<br/><sub>You have signed the CLA already but the status is still pending? Let us [recheck](https://cla-assistant.io/check/replicatedhq/kots?pullRequest=1147) it.</sub>
markpundsack: In the last image, how come the all versions row for the currently deploying version doesn't have a status icon? The app banner does. 

Likewise, how come the older version doesn't show any status at all?

@GraysonNull Thinking about the deploying icon, it looks like a grayed out do-not-enter sign. Can you come up with a better icon indicating ongoing deployment?
GraysonNull: Yeah agreed, it's not a great icon for that. How about just showing the loading spinner in place of an icon? I think that would make sense to show that there plus would give some good feedback that something is actually happening.
markpundsack: Oh interesting, you mean an animated spinner. That's kinda brilliant.
GraysonNull: Yeah exactly, seems kinda like a perfect use for it.
jgruica: @GraysonNull updated code and screenshots with deploying spinner

Patch:
diff --git a/kotsadm/pkg/apiserver/server.go b/kotsadm/pkg/apiserver/server.go
index 64548e9ce..b7dd4cafb 100644
--- a/kotsadm/pkg/apiserver/server.go
+++ b/kotsadm/pkg/apiserver/server.go
@@ -34,8 +34,6 @@ func Start() {
 		panic(err)
 	}
 
-	store.GetStore().RunMigrations()
-
 	if err := informers.Start(); err != nil {
 		log.Println("Failed to start informers", err)
 	}
diff --git a/kotsadm/pkg/handlers/registry.go b/kotsadm/pkg/handlers/registry.go
index fd24a92b3..459602224 100644
--- a/kotsadm/pkg/handlers/registry.go
+++ b/kotsadm/pkg/handlers/registry.go
@@ -4,7 +4,6 @@ import (
 	"encoding/json"
 	"net/http"
 	"net/url"
-	"os"
 
 	"github.com/aws/aws-sdk-go/aws/awserr"
 	"github.com/containers/image/v5/docker"
@@ -12,11 +11,9 @@ import (
 	"github.com/gorilla/mux"
 	"github.com/pkg/errors"
 	"github.com/replicatedhq/kots/kotsadm/pkg/logger"
-	"github.com/replicatedhq/kots/kotsadm/pkg/preflight"
 	"github.com/replicatedhq/kots/kotsadm/pkg/registry"
 	registrytypes "github.com/replicatedhq/kots/kotsadm/pkg/registry/types"
 	"github.com/replicatedhq/kots/kotsadm/pkg/store"
-	"github.com/replicatedhq/kots/kotsadm/pkg/version"
 	dockerregistry "github.com/replicatedhq/kots/pkg/docker/registry"
 )
 
@@ -118,7 +115,7 @@ func UpdateAppRegistry(w http.ResponseWriter, r *http.Request) {
 	if err != nil {
 		logger.Error(err)
 		updateAppRegistryResponse.Error = err.Error()
-		JSON(w, http.StatusInternalServerError, updateAppRegistryResponse)
+		JSON(w, 500, updateAppRegistryResponse)
 		return
 	}
 
@@ -130,12 +127,12 @@ func UpdateAppRegistry(w http.ResponseWriter, r *http.Request) {
 				if err != nil {
 					logger.Error(err)
 					updateAppRegistryResponse.Error = err.Error()
-					JSON(w, http.StatusInternalServerError, updateAppRegistryResponse)
+					JSON(w, 500, updateAppRegistryResponse)
 					return
 				}
 
 				updateAppRegistryResponse.Success = true
-				JSON(w, http.StatusOK, updateAppRegistryResponse)
+				JSON(w, 200, updateAppRegistryResponse)
 				return
 			}
 		}
@@ -144,11 +141,11 @@ func UpdateAppRegistry(w http.ResponseWriter, r *http.Request) {
 	// in a goroutine, start pushing the images to the remote registry
 	// we will let this function return while this happens
 	go func() {
-		appDir, err := registry.RewriteImages(
+		if err := registry.RewriteImages(
 			foundApp.ID, foundApp.CurrentSequence, updateAppRegistryRequest.Hostname,
 			updateAppRegistryRequest.Username, updateAppRegistryRequest.Password,
-			updateAppRegistryRequest.Namespace, nil)
-		if err != nil {
+			updateAppRegistryRequest.Namespace, nil,
+		); err != nil {
 			// log credential errors at info level
 			causeErr := errors.Cause(err)
 			switch causeErr.(type) {
@@ -164,22 +161,6 @@ func UpdateAppRegistry(w http.ResponseWriter, r *http.Request) {
 			}
 			return
 		}
-		defer os.RemoveAll(appDir)
-
-		newSequence, err := version.CreateVersion(foundApp.ID, appDir, "Registry Change", foundApp.CurrentSequence)
-		if err != nil {
-			logger.Error(err)
-			updateAppRegistryResponse.Error = err.Error()
-			JSON(w, http.StatusInternalServerError, updateAppRegistryResponse)
-			return
-		}
-
-		if err := preflight.Run(foundApp.ID, newSequence, foundApp.IsAirgap, appDir); err != nil {
-			logger.Error(err)
-			updateAppRegistryResponse.Error = err.Error()
-			JSON(w, http.StatusInternalServerError, updateAppRegistryResponse)
-			return
-		}
 
 		err = store.GetStore().UpdateRegistry(foundApp.ID, updateAppRegistryRequest.Hostname, updateAppRegistryRequest.Username, updateAppRegistryRequest.Password, updateAppRegistryRequest.Namespace)
 		if err != nil {
diff --git a/kotsadm/pkg/preflight/preflight.go b/kotsadm/pkg/preflight/preflight.go
index 8402f273e..a7388d671 100644
--- a/kotsadm/pkg/preflight/preflight.go
+++ b/kotsadm/pkg/preflight/preflight.go
@@ -9,7 +9,6 @@ import (
 	"github.com/pkg/errors"
 	"github.com/replicatedhq/kots/kotsadm/pkg/downstream"
 	"github.com/replicatedhq/kots/kotsadm/pkg/logger"
-	"github.com/replicatedhq/kots/kotsadm/pkg/registry"
 	"github.com/replicatedhq/kots/kotsadm/pkg/render"
 	"github.com/replicatedhq/kots/kotsadm/pkg/render/helper"
 	"github.com/replicatedhq/kots/kotsadm/pkg/store"
@@ -82,11 +81,6 @@ func Run(appID string, sequence int64, isAirgap bool, archiveDir string) error {
 		if err != nil {
 			return errors.Wrap(err, "failed to load rendered preflight")
 		}
-		collectors, err := registry.UpdateCollectorSpecsWithRegistryData(p.Spec.Collectors, registrySettings, renderedKotsKinds.Installation.Spec.KnownImages, renderedKotsKinds.License)
-		if err != nil {
-			return errors.Wrap(err, "failed to rewrite images in preflight")
-		}
-		p.Spec.Collectors = collectors
 
 		go func() {
 			logger.Debug("preflight checks beginning")
diff --git a/kotsadm/pkg/registry/registry.go b/kotsadm/pkg/registry/registry.go
index a4e4bef30..178deafe0 100644
--- a/kotsadm/pkg/registry/registry.go
+++ b/kotsadm/pkg/registry/registry.go
@@ -12,8 +12,10 @@ import (
 
 	"github.com/pkg/errors"
 	"github.com/replicatedhq/kots/kotsadm/pkg/logger"
+	"github.com/replicatedhq/kots/kotsadm/pkg/preflight"
 	"github.com/replicatedhq/kots/kotsadm/pkg/registry/types"
 	"github.com/replicatedhq/kots/kotsadm/pkg/store"
+	"github.com/replicatedhq/kots/kotsadm/pkg/version"
 	kotsv1beta1 "github.com/replicatedhq/kots/kotskinds/apis/kots/v1beta1"
 	"github.com/replicatedhq/kots/pkg/kotsadm"
 	"github.com/replicatedhq/kots/pkg/kotsutil"
@@ -27,10 +29,9 @@ import (
 
 // RewriteImages will use the app (a) and send the images to the registry specified. It will create patches for these
 // and create a new version of the application
-// the caller is responsible for deleting the appDir returned
-func RewriteImages(appID string, sequence int64, hostname string, username string, password string, namespace string, configValues *kotsv1beta1.ConfigValues) (appDir string, finalError error) {
+func RewriteImages(appID string, sequence int64, hostname string, username string, password string, namespace string, configValues *kotsv1beta1.ConfigValues) (finalError error) {
 	if err := store.GetStore().SetTaskStatus("image-rewrite", "Updating registry settings", "running"); err != nil {
-		return "", errors.Wrap(err, "failed to set task status")
+		return errors.Wrap(err, "failed to set task status")
 	}
 
 	finishedCh := make(chan struct{})
@@ -65,24 +66,24 @@ func RewriteImages(appID string, sequence int64, hostname string, username strin
 	// get the archive and store it in a temporary location
 	appDir, err := store.GetStore().GetAppVersionArchive(appID, sequence)
 	if err != nil {
-		return "", errors.Wrap(err, "failed to get app version archive")
+		return errors.Wrap(err, "failed to get app version archive")
 	}
 	defer os.RemoveAll(appDir)
 
 	installation, err := kotsutil.LoadInstallationFromPath(filepath.Join(appDir, "upstream", "userdata", "installation.yaml"))
 	if err != nil {
-		return "", errors.Wrap(err, "failed to load installation from path")
+		return errors.Wrap(err, "failed to load installation from path")
 	}
 
 	license, err := kotsutil.LoadLicenseFromPath(filepath.Join(appDir, "upstream", "userdata", "license.yaml"))
 	if err != nil {
-		return "", errors.Wrap(err, "failed to load license from path")
+		return errors.Wrap(err, "failed to load license from path")
 	}
 
 	if configValues == nil {
 		previousConfigValues, err := kotsutil.LoadConfigValuesFromFile(filepath.Join(appDir, "upstream", "userdata", "config.yaml"))
 		if err != nil && !os.IsNotExist(errors.Cause(err)) {
-			return "", errors.Wrap(err, "failed to load config values from path")
+			return errors.Wrap(err, "failed to load config values from path")
 		}
 
 		configValues = previousConfigValues
@@ -91,7 +92,7 @@ func RewriteImages(appID string, sequence int64, hostname string, username strin
 	// get the downstream names only
 	downstreams, err := store.GetStore().ListDownstreamsForApp(appID)
 	if err != nil {
-		return "", errors.Wrap(err, "failed to list downstreams")
+		return errors.Wrap(err, "failed to list downstreams")
 	}
 
 	downstreamNames := []string{}
@@ -101,7 +102,7 @@ func RewriteImages(appID string, sequence int64, hostname string, username strin
 
 	a, err := store.GetStore().GetApp(appID)
 	if err != nil {
-		return "", errors.Wrap(err, "failed to get app")
+		return errors.Wrap(err, "failed to get app")
 	}
 
 	// dev_namespace makes the dev env work
@@ -145,10 +146,19 @@ func RewriteImages(appID string, sequence int64, hostname string, username strin
 	}
 
 	if err := rewrite.Rewrite(options); err != nil {
-		return "", errors.Wrap(err, "failed to rewrite images")
+		return errors.Wrap(err, "failed to rewrite images")
 	}
 
-	return appDir, nil
+	newSequence, err := version.CreateVersion(appID, appDir, "Registry Change", a.CurrentSequence)
+	if err != nil {
+		return errors.Wrap(err, "failed to create new version")
+	}
+
+	if err := preflight.Run(appID, newSequence, a.IsAirgap, appDir); err != nil {
+		return errors.Wrap(err, "failed to run preflights")
+	}
+
+	return nil
 }
 
 func HasKurlRegistry() (bool, error) {
diff --git a/kotsadm/pkg/registry/troubleshoot.go b/kotsadm/pkg/registry/troubleshoot.go
deleted file mode 100644
index 392469a49..000000000
--- a/kotsadm/pkg/registry/troubleshoot.go
+++ /dev/null
@@ -1,124 +0,0 @@
-package registry
-
-import (
-	"encoding/base64"
-	"fmt"
-	"strings"
-
-	"github.com/pkg/errors"
-	"github.com/replicatedhq/kots/kotsadm/pkg/registry/types"
-	kotsv1beta1 "github.com/replicatedhq/kots/kotskinds/apis/kots/v1beta1"
-	kotsregistry "github.com/replicatedhq/kots/pkg/docker/registry"
-	troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
-)
-
-// UpdateCollectorSpecsWithRegistryData takes an array of collectors and some environment data (local registry info and license, etc)
-// any image that needs to be rewritten to be compatible with the local registry settings or proxy pull
-// will be updated and replaced in the spec.  any required image pull secret will be automatically
-// inserted into the spec
-// an error is returned if anything failed, but the collectors param can always be used after calling (assuming no error)
-func UpdateCollectorSpecsWithRegistryData(collectors []*troubleshootv1beta2.Collect, localRegistryInfo *types.RegistrySettings, knownImages []kotsv1beta1.InstallationImage, license *kotsv1beta1.License) ([]*troubleshootv1beta2.Collect, error) {
-	// if there's a local registry, always attach that image pull secret for all, and
-	// always rewrite
-	updatedCollectors := make([]*troubleshootv1beta2.Collect, len(collectors))
-
-	if localRegistryInfo != nil {
-		for idx, collect := range collectors {
-			// only the run collector supports an image currently
-			// this is written as in if statement to support additional collectors that include images
-			if collect.Run != nil {
-				run := collect.Run
-
-				run.Image = rewriteImage(localRegistryInfo.Hostname, localRegistryInfo.Namespace, run.Image)
-				pullSecret, err := kotsregistry.PullSecretForRegistries([]string{localRegistryInfo.Hostname}, localRegistryInfo.Username, localRegistryInfo.Password, run.Namespace)
-				if err != nil {
-					return nil, errors.Wrap(err, "failed to generate pull secret for registry")
-				}
-
-				run.ImagePullSecret = &troubleshootv1beta2.ImagePullSecrets{
-					SecretType: "kubernetes.io/dockerconfigjson",
-					Data: map[string]string{
-						".dockerconfigjson": base64.StdEncoding.EncodeToString(pullSecret.Data[".dockerconfigjson"]),
-					},
-				}
-				collect.Run = run
-
-				updatedCollectors[idx] = collect
-
-			} else {
-				updatedCollectors[idx] = collect
-			}
-		}
-
-		return updatedCollectors, nil
-	}
-
-	registryProxyInfo := kotsregistry.ProxyEndpointFromLicense(license)
-
-	// for all known private images, rewrite to the replicated proxy and add license image pull secret
-	for idx, collect := range collectors {
-		// all collectors that include images in the spec should have an if / else statement here
-		if collect.Run != nil {
-			for _, knownImage := range knownImages {
-				if knownImage.Image == collect.Run.Image && knownImage.IsPrivate {
-					run := collect.Run
-
-					// if it's the replicated registry, no change, just add image pull secret
-					registryHost := strings.Split(run.Image, "/")[0]
-					if registryHost != registryProxyInfo.Registry {
-						tag := strings.Split(run.Image, ":")
-						run.Image = kotsregistry.MakeProxiedImageURL(registryProxyInfo.Proxy, license.Spec.AppSlug, run.Image)
-						if len(tag) > 1 {
-							run.Image = fmt.Sprintf("%s:%s", run.Image, tag[len(tag)-1])
-						}
-						pullSecret, err := kotsregistry.PullSecretForRegistries([]string{registryProxyInfo.Proxy}, license.Spec.LicenseID, license.Spec.LicenseID, run.Namespace)
-						if err != nil {
-							return nil, errors.Wrap(err, "failed to generate pull secret for proxy registry")
-						}
-
-						run.ImagePullSecret = &troubleshootv1beta2.ImagePullSecrets{
-							SecretType: "kubernetes.io/dockerconfigjson",
-							Data: map[string]string{
-								".dockerconfigjson": base64.StdEncoding.EncodeToString(pullSecret.Data[".dockerconfigjson"]),
-							},
-						}
-
-						collect.Run = run
-					} else {
-						pullSecret, err := kotsregistry.PullSecretForRegistries([]string{registryProxyInfo.Registry}, license.Spec.LicenseID, license.Spec.LicenseID, run.Namespace)
-						if err != nil {
-							return nil, errors.Wrap(err, "failed to generate pull secret for replicated registry")
-						}
-
-						run.ImagePullSecret = &troubleshootv1beta2.ImagePullSecrets{
-							SecretType: "kubernetes.io/dockerconfigjson",
-							Data: map[string]string{
-								".dockerconfigjson": base64.StdEncoding.EncodeToString(pullSecret.Data[".dockerconfigjson"]),
-							},
-						}
-
-						collect.Run = run
-					}
-
-					collectors[idx].Run = run
-				}
-			}
-
-			updatedCollectors[idx] = collect
-		} else {
-			updatedCollectors[idx] = collect
-		}
-	}
-	return updatedCollectors, nil
-}
-
-func rewriteImage(newHost string, newNamespace string, image string) string {
-	imageParts := strings.Split(image, "/")
-	imageNameWithOptionalTag := imageParts[len(imageParts)-1]
-
-	return strings.Join([]string{
-		newHost,
-		newNamespace,
-		imageNameWithOptionalTag,
-	}, "/")
-}
diff --git a/kotsadm/pkg/store/ocistore/migrations.go b/kotsadm/pkg/store/ocistore/migrations.go
deleted file mode 100644
index dc155434b..000000000
--- a/kotsadm/pkg/store/ocistore/migrations.go
+++ /dev/null
@@ -1,4 +0,0 @@
-package ocistore
-
-func (_ OCIStore) RunMigrations() {
-}
diff --git a/kotsadm/pkg/store/s3pg/migrations.go b/kotsadm/pkg/store/s3pg/migrations.go
deleted file mode 100644
index 3188bfc27..000000000
--- a/kotsadm/pkg/store/s3pg/migrations.go
+++ /dev/null
@@ -1,427 +0,0 @@
-package s3pg
-
-import (
-	"fmt"
-	"os"
-
-	"github.com/pkg/errors"
-	"github.com/replicatedhq/kots/kotsadm/pkg/logger"
-	"github.com/replicatedhq/kots/kotsadm/pkg/persistence"
-	"github.com/replicatedhq/kots/pkg/kotsutil"
-)
-
-func (s S3PGStore) RunMigrations() {
-	if err := s.migrateKotsAppSpec(); err != nil {
-		logger.Error(errors.Wrap(err, "failed to migrate kots_app_spec"))
-	}
-
-	if err := s.migrateKotsInstallationSpec(); err != nil {
-		logger.Error(errors.Wrap(err, "failed to migrate kots_installation_spec"))
-	}
-
-	if err := s.migrateSupportBundleSpec(); err != nil {
-		logger.Error(errors.Wrap(err, "failed to migrate supportbundle_spec"))
-	}
-
-	if err := s.migratePreflightSpec(); err != nil {
-		logger.Error(errors.Wrap(err, "failed to migrate preflight_spec"))
-	}
-
-	if err := s.migrateAnalyzerSpec(); err != nil {
-		logger.Error(errors.Wrap(err, "failed to migrate analyzer_spec"))
-	}
-
-	if err := s.migrateAppSpec(); err != nil {
-		logger.Error(errors.Wrap(err, "failed to migrate app_spec"))
-	}
-}
-
-func (s S3PGStore) migrateKotsAppSpec() error {
-	db := persistence.MustGetPGSession()
-	query := `select app_id, sequence from app_version where kots_app_spec is null or not kots_app_spec like '%apiVersion%'`
-
-	rows, err := db.Query(query)
-	if err != nil {
-		return errors.Wrap(err, "failed to query db")
-	}
-	defer rows.Close()
-
-	type versionType struct {
-		appID    string
-		sequence int64
-	}
-
-	versions := make([]versionType, 0)
-	for rows.Next() {
-		var appID string
-		var sequence int64
-
-		if err := rows.Scan(&appID, &sequence); err != nil {
-			return errors.Wrap(err, "failed to scan")
-		}
-
-		versions = append(versions, versionType{
-			appID:    appID,
-			sequence: sequence,
-		})
-	}
-
-	for _, version := range versions {
-		logger.Info(fmt.Sprintf("Migrating kots_app_spec for app %s sequence %d", version.appID, version.sequence))
-		err := func() error {
-			archiveDir, err := s.GetAppVersionArchive(version.appID, version.sequence)
-			if err != nil {
-				return errors.Wrap(err, "failed to get app version archive")
-			}
-			defer os.RemoveAll(archiveDir)
-
-			kotsKinds, err := kotsutil.LoadKotsKindsFromPath(archiveDir)
-			if err != nil {
-				return errors.Wrap(err, "failed to load kots kinds from path")
-			}
-
-			spec, err := kotsKinds.Marshal("kots.io", "v1beta1", "Application")
-			if err != nil {
-				return errors.Wrap(err, "failed to marshal kots kinds")
-			}
-
-			query := `update app_version set kots_app_spec = $1 where app_id = $2 and sequence = $3`
-			_, err = db.Exec(query, spec, version.appID, version.sequence)
-			if err != nil {
-				return errors.Wrap(err, "failed to set kots_app_spec")
-			}
-
-			return nil
-		}()
-		if err != nil {
-			logger.Error(errors.Wrapf(err, "failed to migrate kots_app_spec, app %v sequece %v", version.appID, version.sequence))
-		}
-	}
-
-	return nil
-}
-
-func (s S3PGStore) migrateKotsInstallationSpec() error {
-	db := persistence.MustGetPGSession()
-	query := `select app_id, sequence from app_version where kots_installation_spec is null or not kots_installation_spec like '%apiVersion%'`
-
-	rows, err := db.Query(query)
-	if err != nil {
-		return errors.Wrap(err, "failed to query db")
-	}
-	defer rows.Close()
-
-	type versionType struct {
-		appID    string
-		sequence int64
-	}
-
-	versions := make([]versionType, 0)
-	for rows.Next() {
-		var appID string
-		var sequence int64
-
-		if err := rows.Scan(&appID, &sequence); err != nil {
-			return errors.Wrap(err, "failed to scan")
-		}
-
-		versions = append(versions, versionType{
-			appID:    appID,
-			sequence: sequence,
-		})
-	}
-
-	for _, version := range versions {
-		logger.Info(fmt.Sprintf("Migrating kots_installation_spec for app %s sequence %d", version.appID, version.sequence))
-		err := func() error {
-			archiveDir, err := s.GetAppVersionArchive(version.appID, version.sequence)
-			if err != nil {
-				return errors.Wrap(err, "failed to get app version archive")
-			}
-			defer os.RemoveAll(archiveDir)
-
-			kotsKinds, err := kotsutil.LoadKotsKindsFromPath(archiveDir)
-			if err != nil {
-				return errors.Wrap(err, "failed to load kots kinds from path")
-			}
-
-			spec, err := kotsKinds.Marshal("kots.io", "v1beta1", "Installation")
-			if err != nil {
-				return errors.Wrap(err, "failed to marshal kots kinds")
-			}
-
-			query := `update app_version set kots_installation_spec = $1 where app_id = $2 and sequence = $3`
-			_, err = db.Exec(query, spec, version.appID, version.sequence)
-			if err != nil {
-				return errors.Wrap(err, "failed to set kots_installation_spec")
-			}
-
-			return nil
-		}()
-		if err != nil {
-			logger.Error(errors.Wrapf(err, "failed to migrate kots_installation_spec app %v sequece %v", version.appID, version.sequence))
-		}
-	}
-
-	return nil
-}
-
-func (s S3PGStore) migrateSupportBundleSpec() error {
-	db := persistence.MustGetPGSession()
-	query := `select app_id, sequence from app_version where supportbundle_spec is null or not supportbundle_spec like '%apiVersion%'`
-
-	rows, err := db.Query(query)
-	if err != nil {
-		return errors.Wrap(err, "failed to query db")
-	}
-	defer rows.Close()
-
-	type versionType struct {
-		appID    string
-		sequence int64
-	}
-
-	versions := make([]versionType, 0)
-	for rows.Next() {
-		var appID string
-		var sequence int64
-
-		if err := rows.Scan(&appID, &sequence); err != nil {
-			return errors.Wrap(err, "failed to scan")
-		}
-
-		versions = append(versions, versionType{
-			appID:    appID,
-			sequence: sequence,
-		})
-	}
-
-	for _, version := range versions {
-		logger.Info(fmt.Sprintf("Migrating kots_installation_spec for app %s sequence %d", version.appID, version.sequence))
-		err := func() error {
-			archiveDir, err := s.GetAppVersionArchive(version.appID, version.sequence)
-			if err != nil {
-				return errors.Wrap(err, "failed to get app version archive")
-			}
-			defer os.RemoveAll(archiveDir)
-
-			kotsKinds, err := kotsutil.LoadKotsKindsFromPath(archiveDir)
-			if err != nil {
-				return errors.Wrap(err, "failed to load kots kinds from path")
-			}
-
-			spec, err := kotsKinds.Marshal("troubleshoot.sh", "v1beta2", "SupportBundle")
-			if err != nil {
-				return errors.Wrap(err, "failed to marshal kots kinds")
-			}
-
-			query := `update app_version set supportbundle_spec = $1 where app_id = $2 and sequence = $3`
-			_, err = db.Exec(query, spec, version.appID, version.sequence)
-			if err != nil {
-				return errors.Wrap(err, "failed to set supportbundle_spec")
-			}
-
-			return nil
-		}()
-		if err != nil {
-			logger.Error(errors.Wrapf(err, "failed to migrate supportbundle_spec app %v sequece %v", version.appID, version.sequence))
-		}
-	}
-
-	return nil
-}
-
-func (s S3PGStore) migratePreflightSpec() error {
-	db := persistence.MustGetPGSession()
-	query := `select app_id, sequence from app_version where preflight_spec is null or not preflight_spec like '%apiVersion%'`
-
-	rows, err := db.Query(query)
-	if err != nil {
-		return errors.Wrap(err, "failed to query db")
-	}
-	defer rows.Close()
-
-	type versionType struct {
-		appID    string
-		sequence int64
-	}
-
-	versions := make([]versionType, 0)
-	for rows.Next() {
-		var appID string
-		var sequence int64
-
-		if err := rows.Scan(&appID, &sequence); err != nil {
-			return errors.Wrap(err, "failed to scan")
-		}
-
-		versions = append(versions, versionType{
-			appID:    appID,
-			sequence: sequence,
-		})
-	}
-
-	for _, version := range versions {
-		logger.Info(fmt.Sprintf("Migrating preflight_spec for app %s sequence %d", version.appID, version.sequence))
-		err := func() error {
-			archiveDir, err := s.GetAppVersionArchive(version.appID, version.sequence)
-			if err != nil {
-				return errors.Wrap(err, "failed to get app version archive")
-			}
-			defer os.RemoveAll(archiveDir)
-
-			kotsKinds, err := kotsutil.LoadKotsKindsFromPath(archiveDir)
-			if err != nil {
-				return errors.Wrap(err, "failed to load kots kinds from path")
-			}
-
-			spec, err := kotsKinds.Marshal("troubleshoot.sh", "v1beta2", "Preflight")
-			if err != nil {
-				return errors.Wrap(err, "failed to marshal kots kinds")
-			}
-
-			query := `update app_version set preflight_spec = $1 where app_id = $2 and sequence = $3`
-			_, err = db.Exec(query, spec, version.appID, version.sequence)
-			if err != nil {
-				return errors.Wrap(err, "failed to set preflight_spec")
-			}
-
-			return nil
-		}()
-		if err != nil {
-			logger.Error(errors.Wrapf(err, "failed to migrate preflight_spec app %v sequece %v", version.appID, version.sequence))
-		}
-	}
-
-	return nil
-}
-
-func (s S3PGStore) migrateAnalyzerSpec() error {
-	db := persistence.MustGetPGSession()
-	query := `select app_id, sequence from app_version where analyzer_spec is null or not analyzer_spec like '%apiVersion%'`
-
-	rows, err := db.Query(query)
-	if err != nil {
-		return errors.Wrap(err, "failed to query db")
-	}
-	defer rows.Close()
-
-	type versionType struct {
-		appID    string
-		sequence int64
-	}
-
-	versions := make([]versionType, 0)
-	for rows.Next() {
-		var appID string
-		var sequence int64
-
-		if err := rows.Scan(&appID, &sequence); err != nil {
-			return errors.Wrap(err, "failed to scan")
-		}
-
-		versions = append(versions, versionType{
-			appID:    appID,
-			sequence: sequence,
-		})
-	}
-
-	for _, version := range versions {
-		logger.Info(fmt.Sprintf("Migrating analyzer_spec for app %s sequence %d", version.appID, version.sequence))
-		err := func() error {
-			archiveDir, err := s.GetAppVersionArchive(version.appID, version.sequence)
-			if err != nil {
-				return errors.Wrap(err, "failed to get app version archive")
-			}
-			defer os.RemoveAll(archiveDir)
-
-			kotsKinds, err := kotsutil.LoadKotsKindsFromPath(archiveDir)
-			if err != nil {
-				return errors.Wrap(err, "failed to load kots kinds from path")
-			}
-
-			spec, err := kotsKinds.Marshal("troubleshoot.sh", "v1beta2", "Analyzer")
-			if err != nil {
-				return errors.Wrap(err, "failed to marshal kots kinds")
-			}
-
-			query := `update app_version set analyzer_spec = $1 where app_id = $2 and sequence = $3`
-			_, err = db.Exec(query, spec, version.appID, version.sequence)
-			if err != nil {
-				return errors.Wrap(err, "failed to set analyzer_spec")
-			}
-
-			return nil
-		}()
-		if err != nil {
-			logger.Error(errors.Wrapf(err, "failed to migrate analyzer_spec app %v sequece %v", version.appID, version.sequence))
-		}
-	}
-
-	return nil
-}
-
-func (s S3PGStore) migrateAppSpec() error {
-	db := persistence.MustGetPGSession()
-	query := `select app_id, sequence from app_version where app_spec is null or not app_spec like '%apiVersion%'`
-
-	rows, err := db.Query(query)
-	if err != nil {
-		return errors.Wrap(err, "failed to query db")
-	}
-	defer rows.Close()
-
-	type versionType struct {
-		appID    string
-		sequence int64
-	}
-
-	versions := make([]versionType, 0)
-	for rows.Next() {
-		var appID string
-		var sequence int64
-
-		if err := rows.Scan(&appID, &sequence); err != nil {
-			return errors.Wrap(err, "failed to scan")
-		}
-
-		versions = append(versions, versionType{
-			appID:    appID,
-			sequence: sequence,
-		})
-	}
-
-	for _, version := range versions {
-		logger.Info(fmt.Sprintf("Migrating app_spec for app %s sequence %d", version.appID, version.sequence))
-		err := func() error {
-			archiveDir, err := s.GetAppVersionArchive(version.appID, version.sequence)
-			if err != nil {
-				return errors.Wrap(err, "failed to get app version archive")
-			}
-			defer os.RemoveAll(archiveDir)
-
-			kotsKinds, err := kotsutil.LoadKotsKindsFromPath(archiveDir)
-			if err != nil {
-				return errors.Wrap(err, "failed to load kots kinds from path")
-			}
-
-			spec, err := kotsKinds.Marshal("app.k8s.io", "v1beta1", "Application")
-			if err != nil {
-				return errors.Wrap(err, "failed to marshal kots kinds")
-			}
-
-			query := `update app_version set app_spec = $1 where app_id = $2 and sequence = $3`
-			_, err = db.Exec(query, spec, version.appID, version.sequence)
-			if err != nil {
-				return errors.Wrap(err, "failed to set app_spec")
-			}
-
-			return nil
-		}()
-		if err != nil {
-			logger.Error(errors.Wrapf(err, "failed to migrate app_spec app %v sequece %v", version.appID, version.sequence))
-		}
-	}
-
-	return nil
-}
diff --git a/kotsadm/pkg/store/store_interface.go b/kotsadm/pkg/store/store_interface.go
index b6a9a224f..afb430e9e 100644
--- a/kotsadm/pkg/store/store_interface.go
+++ b/kotsadm/pkg/store/store_interface.go
@@ -23,7 +23,6 @@ import (
 )
 
 type KOTSStore interface {
-	Migrations
 	RegistryStore
 	SupportBundleStore
 	PreflightStore
@@ -44,10 +43,6 @@ type KOTSStore interface {
 	IsNotFound(err error) bool
 }
 
-type Migrations interface {
-	RunMigrations()
-}
-
 type RegistryStore interface {
 	GetRegistryDetailsForApp(appID string) (*registrytypes.RegistrySettings, error)
 	UpdateRegistry(appID string, hostname string, username string, password string, namespace string) error
diff --git a/kotsadm/pkg/supportbundle/supportbundle.go b/kotsadm/pkg/supportbundle/supportbundle.go
index 6aa455c08..17ecf78a9 100644
--- a/kotsadm/pkg/supportbundle/supportbundle.go
+++ b/kotsadm/pkg/supportbundle/supportbundle.go
@@ -20,13 +20,11 @@ import (
 	"github.com/replicatedhq/kots/kotsadm/pkg/license"
 	"github.com/replicatedhq/kots/kotsadm/pkg/logger"
 	"github.com/replicatedhq/kots/kotsadm/pkg/persistence"
-	"github.com/replicatedhq/kots/kotsadm/pkg/registry"
 	"github.com/replicatedhq/kots/kotsadm/pkg/render/helper"
 	"github.com/replicatedhq/kots/kotsadm/pkg/snapshot"
 	"github.com/replicatedhq/kots/kotsadm/pkg/store"
 	"github.com/replicatedhq/kots/kotsadm/pkg/supportbundle/types"
 	"github.com/replicatedhq/kots/kotskinds/client/kotsclientset/scheme"
-	"github.com/replicatedhq/kots/pkg/kotsutil"
 	"github.com/replicatedhq/kots/pkg/template"
 	troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
 	"github.com/segmentio/ksuid"
@@ -176,41 +174,6 @@ func CreateRenderedSpec(appID string, sequence int64, origin string, inCluster b
 		return errors.Wrap(err, "failed render support bundle spec")
 	}
 
-	// unmarshal the spec, look for image replacements in collectors and then remarshal
-	// we do this after template rendering to support templating and then replacement
-	supportBundle, err = kotsutil.LoadSupportBundleFromContents(renderedSpec)
-	if err != nil {
-		return errors.Wrap(err, "failed to unmarshal rendered support bundle spec")
-	}
-
-	archiveDir, err := store.GetStore().GetAppVersionArchive(appID, sequence)
-	if err != nil {
-		return errors.Wrap(err, "failed to get app version archive")
-	}
-	defer os.RemoveAll(archiveDir)
-
-	kotsKinds, err := kotsutil.LoadKotsKindsFromPath(archiveDir)
-	if err != nil {
-		return errors.Wrap(err, "failed to load rendered kots kinds")
-	}
-
-	registrySettings, err := store.GetStore().GetRegistryDetailsForApp(appID)
-	if err != nil {
-		return errors.Wrap(err, "failed to get registry settings for app")
-	}
-
-	collectors, err := registry.UpdateCollectorSpecsWithRegistryData(supportBundle.Spec.Collectors, registrySettings, kotsKinds.Installation.Spec.KnownImages, kotsKinds.License)
-	if err != nil {
-		return errors.Wrap(err, "failed to update collectors")
-	}
-	supportBundle.Spec.Collectors = collectors
-
-	if err := s.Encode(supportBundle, &b); err != nil {
-		return errors.Wrap(err, "failed to encode support bundle")
-	}
-
-	templatedSpec = b.Bytes()
-
 	cfg, err := config.GetConfig()
 	if err != nil {
 		return errors.Wrap(err, "failed to get cluster config")
diff --git a/kotsadm/pkg/updatechecker/updatechecker.go b/kotsadm/pkg/updatechecker/updatechecker.go
index 37e5113bf..e12207771 100644
--- a/kotsadm/pkg/updatechecker/updatechecker.go
+++ b/kotsadm/pkg/updatechecker/updatechecker.go
@@ -9,8 +9,6 @@ import (
 	"github.com/pkg/errors"
 	"github.com/replicatedhq/kots/kotsadm/pkg/app"
 	"github.com/replicatedhq/kots/kotsadm/pkg/downstream"
-	"github.com/replicatedhq/kots/kotsadm/pkg/k8s"
-	"github.com/replicatedhq/kots/kotsadm/pkg/kurl"
 	"github.com/replicatedhq/kots/kotsadm/pkg/license"
 	"github.com/replicatedhq/kots/kotsadm/pkg/logger"
 	"github.com/replicatedhq/kots/kotsadm/pkg/store"
@@ -18,7 +16,6 @@ import (
 	"github.com/replicatedhq/kots/kotsadm/pkg/version"
 	"github.com/replicatedhq/kots/pkg/kotsutil"
 	kotspull "github.com/replicatedhq/kots/pkg/pull"
-	upstreamtypes "github.com/replicatedhq/kots/pkg/upstream/types"
 	cron "github.com/robfig/cron/v3"
 	"go.uber.org/zap"
 )
@@ -202,14 +199,33 @@ func CheckForUpdates(appID string, deploy bool) (int64, error) {
 		Silent:              false,
 	}
 
-	// add info for reporting purposes
-	r, err := GetReportingInfo(a.ID)
+	// get info about the deployed app version for reporting purposes
+	downstreams, err := store.GetStore().ListDownstreamsForApp(a.ID)
 	if err != nil {
-		logger.Error(errors.Wrap(err, "failed to get reporting info"))
+		return 0, errors.Wrap(err, "failed to list downstreams for app")
+	}
+
+	deployedAppSequence, err := downstream.GetCurrentParentSequence(a.ID, downstreams[0].ClusterID)
+	if err != nil {
+		return 0, errors.Wrap(err, "failed to get current downstream parent sequence")
+	}
+
+	if deployedAppSequence != -1 {
+		deployedArchiveDir, err := store.GetStore().GetAppVersionArchive(a.ID, deployedAppSequence)
+		if err != nil {
+			return 0, errors.Wrap(err, "failed to get app version archive")
+		}
+
+		deployedKotsKinds, err := kotsutil.LoadKotsKindsFromPath(deployedArchiveDir)
+		if err != nil {
+			return 0, errors.Wrap(err, "failed to load kotskinds from path")
+		}
+
+		getUpdatesOptions.DownstreamCursor = deployedKotsKinds.Installation.Spec.UpdateCursor
+		getUpdatesOptions.DownstreamChannelID = deployedKotsKinds.Installation.Spec.ChannelID
+		getUpdatesOptions.DownstreamChannelName = deployedKotsKinds.Installation.Spec.ChannelName
 	}
-	getUpdatesOptions.ReportingInfo = r
 
-	// get updates
 	updates, err := kotspull.GetUpdates(fmt.Sprintf("replicated://%s", kotsKinds.License.Spec.AppSlug), getUpdatesOptions)
 	if err != nil {
 		return 0, errors.Wrap(err, "failed to get updates")
@@ -289,63 +305,3 @@ func CheckForUpdates(appID string, deploy bool) (int64, error) {
 
 	return availableUpdates, nil
 }
-
-func GetReportingInfo(appID string) (*upstreamtypes.ReportingInfo, error) {
-	r := upstreamtypes.ReportingInfo{
-		InstanceID: appID,
-	}
-
-	downstreams, err := store.GetStore().ListDownstreamsForApp(appID)
-	if err != nil {
-		return nil, errors.Wrap(err, "failed to list downstreams for app")
-	}
-	if len(downstreams) == 0 {
-		return nil, errors.New("no downstreams found for app")
-	}
-	r.ClusterID = downstreams[0].ClusterID
-
-	deployedAppSequence, err := downstream.GetCurrentParentSequence(appID, downstreams[0].ClusterID)
-	if err != nil {
-		return nil, errors.Wrap(err, "failed to get current downstream parent sequence")
-	}
-
-	// info about the deployed app sequence
-	if deployedAppSequence != -1 {
-		deployedArchiveDir, err := store.GetStore().GetAppVersionArchive(appID, deployedAppSequence)
-		if err != nil {
-			return nil, errors.Wrap(err, "failed to get app version archive")
-		}
-
-		deployedKotsKinds, err := kotsutil.LoadKotsKindsFromPath(deployedArchiveDir)
-		if err != nil {
-			return nil, errors.Wrap(err, "failed to load kotskinds from path")
-		}
-
-		r.DownstreamCursor = deployedKotsKinds.Installation.Spec.UpdateCursor
-		r.DownstreamChannelID = deployedKotsKinds.Installation.Spec.ChannelID
-		r.DownstreamChannelName = deployedKotsKinds.Installation.Spec.ChannelName
-	}
-
-	// get kubernetes cluster version
-	clientset, err := k8s.Clientset()
-	if err != nil {
-		return nil, errors.Wrap(err, "failed to create kubernetes clientset")
-	}
-	k8sVersion, err := clientset.Discovery().ServerVersion()
-	if err != nil {
-		return nil, errors.Wrap(err, "failed to get kubernetes server version")
-	}
-	r.K8sVersion = k8sVersion.GitVersion
-
-	// get app status
-	appStatus, err := store.GetStore().GetAppStatus(appID)
-	if err != nil {
-		return nil, errors.Wrap(err, "failed to get app status")
-	}
-	r.AppStatus = string(appStatus.State)
-
-	// check if embedded cluster
-	r.IsKurl = kurl.IsKurl()
-
-	return &r, nil
-}
diff --git a/kotsadm/web/src/components/PreflightResultPage.jsx b/kotsadm/web/src/components/PreflightResultPage.jsx
index 3246c1788..e67ccd23e 100644
--- a/kotsadm/web/src/components/PreflightResultPage.jsx
+++ b/kotsadm/web/src/components/PreflightResultPage.jsx
@@ -49,7 +49,7 @@ class PreflightResultPage extends Component {
         await this.deployKotsVersion(preflightResultData.appSlug, sequence);
       }
 
-      history.push(`/app/${preflightResultData.appSlug}/version-history`);
+      history.push(`/app/${preflightResultData.appSlug}`);
     } catch (err) {
       console.log(err);
       this.setState({
diff --git a/kotsadm/web/src/components/apps/AppVersionHistory.jsx b/kotsadm/web/src/components/apps/AppVersionHistory.jsx
index ea381f9ee..9665abcc4 100644
--- a/kotsadm/web/src/components/apps/AppVersionHistory.jsx
+++ b/kotsadm/web/src/components/apps/AppVersionHistory.jsx
@@ -4,11 +4,9 @@ import { withRouter, Link } from "react-router-dom";
 import Helmet from "react-helmet";
 import dayjs from "dayjs";
 import ReactTooltip from "react-tooltip"
-import MonacoEditor from "react-monaco-editor";
 import relativeTime from "dayjs/plugin/relativeTime";
 import Modal from "react-modal";
 import moment from "moment";
-import changeCase from "change-case";
 import find from "lodash/find";
 
 import Loader from "../shared/Loader";
@@ -18,6 +16,7 @@ import DownstreamWatchVersionDiff from "@src/components/watches/DownstreamWatchV
 import AirgapUploadProgress from "@src/components/AirgapUploadProgress";
 import UpdateCheckerModal from "@src/components/modals/UpdateCheckerModal";
 import ShowDetailsModal from "@src/components/modals/ShowDetailsModal";
+import ShowLogsModal from "@src/components/modals/ShowLogsModal";
 import ErrorModal from "../modals/ErrorModal";
 import { Utilities, isAwaitingResults, secondsAgo, getPreflightResultState, getGitProviderDiffUrl, getCommitHashFromUrl } from "../../utilities/utilities";
 import { Repeater } from "../../utilities/repeater";
@@ -217,20 +216,9 @@ class AppVersionHistory extends Component {
     }
   }
 
-  renderVersionSequence = version => {
-    return (
-      <div className="flex">
-        {version.sequence}
-        {version.releaseNotes &&
-          <span className="replicated-link u-marginLeft--5" style={{ fontSize: 12, marginTop: 2 }} onClick={() => this.showDownstreamReleaseNotes(version.releaseNotes)}>Release notes</span>
-        }
-      </div>
-    );
-  }
-
   renderYamlErrors = (yamlErrorsDetails, version) => {
     return (
-      <div className="flex alignItems--center">
+      <div className="flex alignItems--center u-marginLeft--5">
         <span className="icon error-small" />
         <span className="u-fontSize--small u-fontWeight--medium u-lineHeight--normal u-marginLeft--5 u-color--red">{yamlErrorsDetails?.length} Invalid file{yamlErrorsDetails?.length !== 1 ? "s" : ""} </span>
         <span className="replicated-link u-marginLeft--5 u-fontSize--small" onClick={() => this.toggleShowDetailsModal(yamlErrorsDetails, version.sequence)}> See details </span>
@@ -246,7 +234,7 @@ class AppVersionHistory extends Component {
 
     if (hasDiffSummaryError) {
       return (
-        <div className="flex flex1 alignItems--center u-marginRight--10">
+        <div className="flex flex1 alignItems--center">
           <span className="u-fontSize--small u-fontWeight--medium u-lineHeight--normal u-color--dustyGray">Cannot generate diff <span className="replicated-link" onClick={() => this.toggleDiffErrModal(version)}>Why?</span></span>
         </div>
       );
@@ -340,12 +328,13 @@ class AppVersionHistory extends Component {
     const downstream = app.downstreams[0];
     const clusterSlug = downstream.cluster?.slug;
     return (
-      <Link to={`/app/${match.params.slug}/downstreams/${clusterSlug}/version-history/preflight/${version.sequence}`}>
+      <Link className="u-marginTop--10" to={`/app/${match.params.slug}/downstreams/${clusterSlug}/version-history/preflight/${version?.sequence}`}>
         <span className="replicated-link" style={{ fontSize: 12 }}>View preflight results</span>
       </Link>
     );
   }
 
+
   renderVersionStatus = version => {
     const { app, match } = this.props;
     const downstream = app.downstreams?.length && app.downstreams[0];
@@ -367,7 +356,7 @@ class AppVersionHistory extends Component {
 
     if (isPastVersion && app.hasPreflight) {
       if (preflightsFailed) {
-        preflightBlock = (<Link to={`/app/${match.params.slug}/downstreams/${clusterSlug}/version-history/preflight/${version.sequence}`} className="replicated-link u-marginLeft--5 u-fontSize--small">View preflight errors</Link>);
+        preflightBlock = (<Link to={`/app/${match.params.slug}/downstreams/${clusterSlug}/version-history/preflight/${version.sequence}`} className="replicated-link u-marginLeft--5 u-fontSize--small">See details</Link>);
       } else if (version.status !== "pending_config") {
         preflightBlock = (<Link to={`/app/${match.params.slug}/downstreams/${clusterSlug}/version-history/preflight/${version.sequence}`} className="replicated-link u-marginLeft--5 u-fontSize--small">View preflights</Link>);
       }
@@ -379,7 +368,7 @@ class AppVersionHistory extends Component {
         </span>);
     } else if (app.hasPreflight) {
       if (preflightsFailed) {
-        preflightBlock = (<Link to={`/app/${match.params.slug}/downstreams/${clusterSlug}/version-history/preflight/${version.sequence}`} className="replicated-link u-marginLeft--5 u-fontSize--small">View preflight errors</Link>);
+        preflightBlock = (<Link to={`/app/${match.params.slug}/downstreams/${clusterSlug}/version-history/preflight/${version.sequence}`} className="replicated-link u-marginLeft--5 u-fontSize--small">See details</Link>);
       } else if (version.status !== "pending_config") {
         preflightBlock = (<Link to={`/app/${match.params.slug}/downstreams/${clusterSlug}/version-history/preflight/${version.sequence}`} className="replicated-link u-marginLeft--5 u-fontSize--small">View preflights</Link>);
       }
@@ -399,6 +388,7 @@ class AppVersionHistory extends Component {
                 "error-small": version.status === "failed" || preflightsFailed
               })}
             />
+            {version.status === "deploying" &&  <Loader className="flex alignItems--center" size="20" />}
             <span className={classNames("u-fontSize--small u-fontWeight--medium u-lineHeight--normal u-marginLeft--5", {
               "u-color--nevada": version.status === "deployed" || version.status === "merged",
               "u-color--orange": version.status === "opened",
@@ -418,7 +408,7 @@ class AppVersionHistory extends Component {
           </div>
           {preflightBlock}
           {version.status === "failed" &&
-            <span className="replicated-link u-marginLeft--5 u-fontSize--small" onClick={() => this.handleViewLogs(version)}>View logs</span>
+            <span className="replicated-link u-marginLeft--5 u-fontSize--small" onClick={() => this.handleViewLogs(version, true)}>View logs</span>
           }
         </div>
       );
@@ -452,7 +442,7 @@ class AppVersionHistory extends Component {
           </div>
           {preflightBlock}
           {version.status === "failed" &&
-            <span className="replicated-link u-marginLeft--5 u-fontSize--small" onClick={() => this.handleViewLogs(version)}>View logs</span>
+            <span className="replicated-link u-marginLeft--5 u-fontSize--small" onClick={() => this.handleViewLogs(version, true)}>View logs</span>
           }
         </div>
       );
@@ -571,7 +561,7 @@ class AppVersionHistory extends Component {
     });
   }
 
-  handleViewLogs = async version => {
+  handleViewLogs = async (version, isFailing) => {
     try {
       const { app } = this.props;
       const clusterId = app.downstreams?.length && app.downstreams[0].cluster?.id;
@@ -587,7 +577,12 @@ class AppVersionHistory extends Component {
       });
       if (res.ok && res.status === 200) {
         const response = await res.json();
-        const selectedTab = Object.keys(response.logs)[0];
+        let selectedTab;
+        if (isFailing) {
+          selectedTab = "applyStderr";
+        } else {
+          selectedTab = Object.keys(response.logs)[0];
+        }
         this.setState({ logs: response.logs, selectedTab, logsLoading: false, viewLogsErrMsg: "" });
       } else {
         this.setState({ logsLoading: false, viewLogsErrMsg: `Failed to view logs, unexpected status code, ${res.status}` });
@@ -872,6 +867,22 @@ class AppVersionHistory extends Component {
     this.setState({ displayShowDetailsModal: !this.state.displayShowDetailsModal, deployView: false, yamlErrorDetails, selectedSequence });
   }
 
+  getCurrentVersionStatus = (version) => {
+    if (version?.status === "deployed" || version?.status === "merged" || version?.status === "pending") {
+      return <span className="u-fontSize--small u-lineHeight--normal u-color--dustyGray u-fontWeight--medium flex alignItems--center u-marginTop--8"> <span className="icon checkmark-icon u-marginRight--5" /> {Utilities.toTitleCase(version?.status).replace("_", " ")} </span>
+    } else if (version?.status === "failed") {
+      return <span className="u-fontSize--small u-lineHeight--normal u-color--red u-fontWeight--medium flex alignItems--center u-marginTop--8"> <span className="icon error-small u-marginRight--5" /> Failed <span className="u-marginLeft--5 replicated-link u-fontSize--small" onClick={() => this.handleViewLogs(version, true)}> See details </span></span>
+    } else if (version?.status === "deploying") {
+      return (
+        <span className="flex alignItems--center u-fontSize--small u-lineHeight--normal u-color--dustyGray u-fontWeight--medium u-marginTop--8">
+          <Loader className="flex alignItems--center u-marginRight--5" size="16" />
+            Deploying
+        </span>);
+    } else {
+      return <span className="u-fontSize--small u-lineHeight--normal u-color--dustyGray u-fontWeight--medium flex alignItems--center u-marginTop--8"> {Utilities.toTitleCase(version?.status).replace("_", " ")} </span>
+    }
+  }
+
   render() {
     const {
       app,
@@ -905,7 +916,6 @@ class AppVersionHistory extends Component {
       uploadResuming,
       noUpdateAvailiableText,
       showUpdateCheckerModal,
-
       loadingVersionHistory,
       versionHistory,
       errorTitle,
@@ -944,7 +954,6 @@ class AppVersionHistory extends Component {
     }
 
     const errorText = checkingUpdateMessage ? checkingUpdateMessage : "Error checking for updates, please try again";
-
     let updateText;
     if (airgapUploadError) {
       updateText = <p className="u-marginTop--10 u-fontSize--small u-color--chestnut u-fontWeight--medium">{airgapUploadError}</p>;
@@ -970,7 +979,7 @@ class AppVersionHistory extends Component {
     } else if (checkingForUpdates) {
       updateText = <p className="u-marginTop--10 u-fontSize--small u-color--dustyGray u-fontWeight--medium">{checkingUpdateTextShort}</p>
     } else if (app.lastUpdateCheckAt && !noUpdateAvailiableText) {
-      updateText = <p className="u-marginTop--10 u-fontSize--small u-color--dustyGray u-fontWeight--medium">Last checked {dayjs(app.lastUpdateCheckAt).fromNow()}</p>;
+      updateText = <p className="u-marginTop--10 u-fontSize--small u-color--silverSand u-fontWeight--medium">Last checked {dayjs(app.lastUpdateCheckAt).fromNow()}</p>;
     } else if (!app.lastUpdateCheckat) {
       updateText = null;
     }
@@ -987,11 +996,12 @@ class AppVersionHistory extends Component {
     const downstream = app.downstreams.length && app.downstreams[0];
     const gitopsEnabled = downstream.gitops?.enabled;
     const currentDownstreamVersion = downstream?.currentVersion;
-    const yamlErrorsDetails = this.yamlErrorsDetails(downstream, currentDownstreamVersion);
 
     // This is kinda hacky. This finds the equivalent downstream version because the midstream
     // version type does not contain metadata like version label or release notes.
     const currentMidstreamVersion = versionHistory.find(version => version.parentSequence === app.currentVersion.sequence) || app.currentVersion;
+    const pendingVersions = downstream?.pendingVersions;
+
 
     return (
       <div className="flex flex-column flex1 u-position--relative u-overflow--auto u-padding--20">
@@ -999,7 +1009,7 @@ class AppVersionHistory extends Component {
           <title>{`${app.name} Version History`}</title>
         </Helmet>
         <div className="flex flex-auto alignItems--center justifyContent--center u-marginTop--10 u-marginBottom--30">
-          <div className="upstream-version-box-wrapper flex">
+          <div className="upstream-version-box-wrapper flex flex1">
             <div className="flex flex1">
               {app.iconUri &&
                 <div className="flex-auto u-marginRight--10">
@@ -1007,35 +1017,75 @@ class AppVersionHistory extends Component {
                 </div>
               }
               <div className="flex1 flex-column">
-                <p className="u-fontSize--34 u-fontWeight--bold u-color--tuna">
-                  {currentMidstreamVersion ? currentMidstreamVersion.versionLabel : "---"}
-                </p>
-                <p className="u-fontSize--large u-fontWeight--medium u-marginTop--5 u-color--nevada">{currentMidstreamVersion ? "Current upstream version" : "No deployments have been made"}</p>
-                <p className="u-marginTop--10 u-fontSize--small u-color--dustyGray u-fontWeight--medium">
-                  {currentMidstreamVersion?.deployedAt && `Released on ${dayjs(currentMidstreamVersion.deployedAt).format("MMMM D, YYYY")}`}
-                  {currentMidstreamVersion?.releaseNotes && <span className={classNames("release-notes-link", { "u-paddingLeft--5": currentMidstreamVersion?.deployedAt })} onClick={this.showReleaseNotes}>Release Notes</span>}
-                </p>
+                <p className="u-fontSize--small u-fontWeight--bold u-lineHeight--normal u-color--tuna"> Current version </p>
+                <div className="flex alignItems--center u-marginTop--5">
+                  <p className="u-fontSize--header2 u-fontWeight--bold u-color--tuna"> {currentDownstreamVersion ? currentDownstreamVersion.versionLabel : "---"}</p>
+                  <p className="u-fontSize--small u-lineHeight--normal u-color--tundora u-fontWeight--medium u-marginLeft--10"> {currentDownstreamVersion ? `Sequence ${currentDownstreamVersion?.sequence}` : null}</p>
+                </div>
+                {currentDownstreamVersion?.deployedAt ? <p className="u-fontSize--small u-lineHeight--normal u-color--silverSand u-fontWeight--medium u-marginTop--5">{`${dayjs(currentDownstreamVersion.deployedAt).format("MMMM D, YYYY  @ hh:mm a")}`}</p> : null}
+                {currentDownstreamVersion && this.getCurrentVersionStatus(currentDownstreamVersion)}
+                {currentDownstreamVersion ?
+                  <div className="flex alignItems--center u-marginTop--8 u-marginTop--8">
+                    {currentDownstreamVersion?.releaseNotes &&
+                      <div>
+                        <span className="icon releaseNotes--icon u-marginRight--10 u-cursor--pointer" onClick={() => this.showDownstreamReleaseNotes(currentDownstreamVersion?.releaseNotes)} data-tip="View release notes" />
+                        <ReactTooltip effect="solid" className="replicated-tooltip" />
+                      </div>}
+                    <div>
+                      <Link to={`/app/${match.params.slug}/downstreams/${app.downstreams[0].cluster?.slug}/version-history/preflight/${currentDownstreamVersion?.sequence}`}
+                        className="icon preflightChecks--icon u-marginRight--10 u-cursor--pointer"
+                        data-tip="View preflight checks" />
+                      <ReactTooltip effect="solid" className="replicated-tooltip" />
+                    </div>
+                    <div>
+                      <span className="icon deployLogs--icon u-marginRight--10 u-cursor--pointer" onClick={() => this.handleViewLogs(currentDownstreamVersion)} data-tip="View deploy logs" />
+                      <ReactTooltip effect="solid" className="replicated-tooltip" />
+                    </div>
+                    {app.isConfigurable &&
+                      <div>
+                        <Link to={`/app/${match.params.slug}/config`} className="icon config--icon u-cursor--pointer" data-tip="Edit config" />
+                        <ReactTooltip effect="solid" className="replicated-tooltip" />
+                      </div>}
+                  </div> : null}
               </div>
             </div>
             {!app.cluster &&
-              <div className="flex-auto flex-column alignItems--center justifyContent--center">
+              <div className={`flex flex1 justifyContent--center ${checkingForUpdates && !isBundleUploading && "alignItems--center"}`}>
                 {checkingForUpdates && !isBundleUploading
                   ? <Loader size="32" />
-                  : showAirgapUI
-                    ?
-                    <MountAware id="bundle-dropzone" onMount={el => this.airgapUploader.assignElement(el)}>
+                  : showAirgapUI ?
+                    <MountAware className="flex alignItems--center" id="bundle-dropzone" onMount={el => this.airgapUploader.assignElement(el)}>
                       <span className="btn secondary blue">Upload new version</span>
                     </MountAware>
                     : showOnlineUI ?
-                      <div className="flex alignItems--center">
-                        <button className="btn secondary blue" onClick={this.onCheckForUpdates}>Check for updates</button>
-                        <span className="icon settings-small-icon u-marginLeft--5 u-cursor--pointer" onClick={this.showUpdateCheckerModal} data-tip="Configure automatic updates"></span>
-                        <ReactTooltip effect="solid" className="replicated-tooltip" />
+                      <div className="flex1 flex-column">
+                        {pendingVersions?.length > 0 ?
+                          <div className="flex flex-column">
+                            <p className="u-fontSize--small u-lineHeight--normal u-color--selectiveYellow u-fontWeight--bold">New version available</p>
+                            <div className="flex flex-column u-marginTop--5">
+                              <div className="flex flex1 alignItems--center">
+                                <span className="u-fontSize--larger u-lineHeight--medium u-fontWeight--bold u-color--tundora">{pendingVersions[0]?.versionLabel}</span>
+                                <span className="u-fontSize--small u-lineHeight--normal u-fontWeight--medium u-color--tundora u-marginLeft--5"> Sequence {pendingVersions[0]?.sequence}</span>
+                              </div>
+                              <div className="flex flex1 alignItems--center">
+                                {pendingVersions[0]?.createdOn ? <p className="u-fontSize--small u-lineHeight--normal u-fontWeight--medium u-color--dustyGray">Released <span className="u-fontWeight--bold">{`${dayjs(pendingVersions[0]?.createdOn).format("MMMM D, YYYY")}`}</span></p> : null}
+                                {pendingVersions[0]?.releaseNotes ? <span className="release-notes-link u-fontSize--small u-fontWeight--medium u-marginLeft--5 flex alignItems--center" onClick={() => this.showDownstreamReleaseNotes(pendingVersions[0]?.releaseNotes)}><span className="icon releaseNotes-small--icon u-marginRight--5" />Release notes</span> : null}
+                              </div>
+                            </div>
+                          </div>
+                          : <p className="u-fontSize--small u-fontWeight--bold u-lineHeight--normal u-color--dustyGray"> No new version available </p>}
+                        <div className="flex alignItems--center u-marginTop--10">
+                          <button className="btn primary blue" onClick={this.onCheckForUpdates}>Check for update</button>
+                          <span className="icon settings-small-icon u-marginLeft--5 u-cursor--pointer" onClick={this.showUpdateCheckerModal} data-tip="Configure automatic update checks"></span>
+                          <ReactTooltip effect="solid" className="replicated-tooltip" />
+                        </div>
+                        {updateText}
+                        {noUpdateAvailiableMsg}
                       </div>
                       : null
                 }
-                {updateText}
-                {noUpdateAvailiableMsg}
+                {!showOnlineUI && updateText}
+                {!showOnlineUI && noUpdateAvailiableMsg}
               </div>
             }
           </div>
@@ -1058,41 +1108,6 @@ class AppVersionHistory extends Component {
                     <p className="err">{makingCurrentVersionErrMsg}</p>
                   </div>
                 </div>}
-              {/* Active downstream */}
-              {!gitopsEnabled && currentDownstreamVersion &&
-                <div className="TableDiff--Wrapper u-marginBottom--30">
-                  <p className="u-fontSize--larger u-fontWeight--bold u-color--tuna u-paddingBottom--10 u-lineHeight--normal">Deployed version</p>
-                  <div className={`VersionHistoryDeploymentRow active-deploy-row ${currentDownstreamVersion.status} flex flex-auto`}>
-                    <div className="flex-column flex1 u-paddingRight--20">
-                      <div>
-                        <p className="u-fontSize--normal u-color--dustyGray">Environment: <span className="u-fontWeight--bold u-color--tuna">{changeCase.title(downstream.name)}</span></p>
-                        <p className="u-fontSize--small u-marginTop--10 u-color--dustyGray">Received: <span className="u-fontWeight--bold u-color--tuna">{moment(currentDownstreamVersion.createdOn).format("MM/DD/YY @ hh:mm a")}</span></p>
-                      </div>
-                      <div className="flex flex1 u-marginTop--15">
-                        <p className="u-fontSize--normal u-color--dustyGray">Upstream: <span className="u-fontWeight--bold u-color--tuna">{currentDownstreamVersion.versionLabel}</span></p>
-                        <div className="u-fontSize--normal u-color--dustyGray u-marginLeft--20 flex">Sequence: <span className="u-fontWeight--bold u-color--tuna u-marginLeft--5">{this.renderVersionSequence(currentDownstreamVersion)}</span></div>
-                      </div>
-                    </div>
-                    <div className="flex-column flex1">
-                      <div>
-                        <p className="u-fontSize--normal u-color--dustyGray">Source: <span className="u-fontWeight--bold u-color--tuna">{currentDownstreamVersion.source}</span></p>
-                        <div className="flex alignItems--center u-fontSize--small u-marginTop--10 u-color--dustyGray">
-                          {this.renderSourceAndDiff(currentDownstreamVersion)}
-                          {yamlErrorsDetails && this.renderYamlErrors(yamlErrorsDetails, currentDownstreamVersion)}
-                        </div>
-                      </div>
-                      <div className="flex flex1 u-fontSize--normal u-color--dustyGray u-marginTop--15 alignItems--center">Status:<span className="u-marginLeft--5">{gitopsEnabled ? this.renderViewPreflights(currentDownstreamVersion) : this.renderVersionStatus(currentDownstreamVersion)}</span></div>
-                    </div>
-                    <div className="flex-column flex1 alignItems--flexEnd">
-                      <div className="flex alignItems--center">
-                        <button className="btn secondary" onClick={() => this.handleViewLogs(currentDownstreamVersion)}>View logs</button>
-                        {app.isConfigurable && <Link className="btn secondary blue u-marginLeft--10" to={`/app/${match.params.slug}/config`}>Edit config</Link>}
-                      </div>
-                      <p className="u-fontSize--normal u-color--dustyGray u-marginTop--15">Deployed: <span className="u-fontWeight--bold u-color--tuna">{moment(currentDownstreamVersion.deployedAt).format("MM/DD/YY @ hh:mm a")}</span></p>
-                    </div>
-                  </div>
-                </div>
-              }
 
               <div className="TableDiff--Wrapper flex-column flex1">
                 <div className={`flex-column flex1 ${showDiffOverlay ? "u-visibility--hidden" : ""}`}>
@@ -1114,30 +1129,32 @@ class AppVersionHistory extends Component {
                       >
                         {selectedDiffReleases && <div className={classNames("checkbox u-marginRight--20", { "checked": (isChecked && !nothingToCommit) }, { "disabled": nothingToCommit })} />}
                         <div className={`${nothingToCommit && selectedDiffReleases && "u-opacity--half"} flex-column flex1 u-paddingRight--20`}>
-                          <div>
-                            <p className="u-fontSize--normal u-color--dustyGray">Environment: <span className="u-fontWeight--bold u-color--tuna">{changeCase.title(downstream.name)}</span></p>
-                            <p className="u-fontSize--small u-marginTop--10 u-color--dustyGray">Received: <span className="u-fontWeight--bold u-color--tuna">{moment(version.createdOn).format("MM/DD/YY @ hh:mm a")}</span></p>
+                          <div className="flex alignItems--center">
+                            <p className="u-fontSize--large u-fontWeight--bold u-lineHeight--medium u-color--tuna">{version.versionLabel || version.title}</p>
+                            <p className="u-fontSize--small u-fontWeight--medium u-lineHeight--normal u-color--tundora u-marginLeft--5" style={{ marginTop: "2px" }}>Sequence {version.sequence}</p>
                           </div>
-                          <div className="flex flex1 u-marginTop--15">
-                            <p className="u-fontSize--normal u-color--dustyGray">Upstream: <span className="u-fontWeight--bold u-color--tuna">{version.versionLabel || version.title}</span></p>
-                            <div className="u-fontSize--normal u-color--dustyGray u-marginLeft--20 flex">Sequence: <span className=" u-fontWeight--bold u-color--tuna u-marginLeft--5">{this.renderVersionSequence(version)}</span></div>
+                          <div className="flex alignItems--center u-marginTop--10"></div>
+                          <div className="flex flex1 u-marginTop--15 alignItems--center">
+                            <p className="u-fontSize--small u-lineHeight--normal u-color--dustyGray u-fontWeight--medium">Released <span className="u-fontWeight--bold">{moment(version.createdOn).format("MM/DD/YY @ hh:mm a")}</span></p>
+                            {version.releaseNotes ?
+                              <p className="release-notes-link u-fontSize--small u-lineHeight--normal u-marginLeft--5 flex alignItems--center" onClick={() => this.showDownstreamReleaseNotes(version.releaseNotes)}> <span className="icon releaseNotes-small--icon u-marginRight--5" />Release notes</p> : null}
                           </div>
                         </div>
                         <div className={`${nothingToCommit && selectedDiffReleases && "u-opacity--half"} flex-column flex1`}>
-                          <div>
-                            <p className="u-fontSize--normal u-color--dustyGray">Source: <span className="u-fontWeight--bold u-color--tuna">{version.source}</span></p>
+                          <div className="flex flex-column">
+                            <p className="u-fontSize--normal u-fontWeight--bold u-color--tuna">{version.source}</p>
                             <div className="flex alignItems--center u-fontSize--small u-marginTop--10 u-color--dustyGray">
                               {this.renderSourceAndDiff(version)}
                               {yamlErrorsDetails && this.renderYamlErrors(yamlErrorsDetails, version)}
                             </div>
                           </div>
-                          <div className="flex flex1 u-fontSize--normal u-color--dustyGray u-marginTop--15 alignItems--center">Status: <span className="u-marginLeft--5">{gitopsEnabled ? this.renderViewPreflights(version) : this.renderVersionStatus(version)}</span></div>
+                          <div className="flex flex1 alignItems--flexEnd"> {gitopsEnabled ? this.renderViewPreflights(version) : this.renderVersionStatus(version)}</div>
                         </div>
                         <div className={`${nothingToCommit && selectedDiffReleases && "u-opacity--half"} flex-column flex1 alignItems--flexEnd`}>
                           <div>
                             {this.renderVersionAction(version, nothingToCommit && selectedDiffReleases)}
                           </div>
-                          <p className="u-fontSize--normal u-color--dustyGray u-marginTop--15">Deployed: <span className="u-fontWeight--bold u-color--tuna">{version.deployedAt ? moment(version.deployedAt).format("MM/DD/YY @ hh:mm a") : "N/A"}</span></p>
+                          <p className="u-fontSize--small u-lineHeight--normal u-color--dustyGray u-fontWeight--medium u-marginTop--15">Deployed: <span className="u-fontWeight--bold">{version.deployedAt ? moment(version.deployedAt).format("MM/DD/YY @ hh:mm a") : "N/A"}</span></p>
                         </div>
                       </div>
                     );
@@ -1183,53 +1200,16 @@ class AppVersionHistory extends Component {
           </div>
         </Modal>
 
-        <Modal
-          isOpen={showLogsModal}
-          onRequestClose={this.hideLogsModal}
-          shouldReturnFocusAfterClose={false}
-          contentLabel="View logs"
-          ariaHideApp={false}
-          className="Modal logs-modal"
-        >
-          <div className="Modal-body flex flex1">
-            {this.state.viewLogsErrMsg ?
-              <div class="flex1 flex-column justifyContent--center alignItems--center">
-                <span className="icon redWarningIcon" />
-                <p className="u-color--chestnut u-fontSize--normal u-fontWeight--medium u-lineHeight--normal u-marginTop--10">{this.state.viewLogsErrMsg}</p>
-              </div>
-              :
-              !logs || !selectedTab || logsLoading ? (
-                <div className="flex-column flex1 alignItems--center justifyContent--center">
-                  <Loader size="60" />
-                </div>
-              ) : (
-                  <div className="flex-column flex1">
-                    <div className="flex-column flex1">
-                      {!logs.renderError && this.renderLogsTabs()}
-                      <div className="flex-column flex1 u-border--gray monaco-editor-wrapper">
-                        <MonacoEditor
-                          language="json"
-                          value={logs.renderError || logs[selectedTab]}
-                          height="100%"
-                          width="100%"
-                          options={{
-                            readOnly: true,
-                            contextmenu: false,
-                            minimap: {
-                              enabled: false
-                            },
-                            scrollBeyondLastLine: false,
-                          }}
-                        />
-                      </div>
-                    </div>
-                    <div className="u-marginTop--20 flex">
-                      <button type="button" className="btn primary" onClick={this.hideLogsModal}>Ok, got it!</button>
-                    </div>
-                  </div>
-                )}
-          </div>
-        </Modal>
+        {showLogsModal &&
+          <ShowLogsModal
+            showLogsModal={showLogsModal}
+            hideLogsModal={this.hideLogsModal}
+            viewLogsErrMsg={this.state.viewLogsErrMsg}
+            logs={logs}
+            selectedTab={selectedTab}
+            logsLoading={logsLoading}
+            renderLogsTabs={this.renderLogsTabs()}
+          />}
 
         <Modal
           isOpen={showDeployWarningModal}
diff --git a/kotsadm/web/src/components/apps/Dashboard.jsx b/kotsadm/web/src/components/apps/Dashboard.jsx
index c278546b3..63aed699b 100644
--- a/kotsadm/web/src/components/apps/Dashboard.jsx
+++ b/kotsadm/web/src/components/apps/Dashboard.jsx
@@ -569,7 +569,7 @@ class Dashboard extends Component {
                 app={app}
               />
               <DashboardCard
-                cardName={`Version: ${currentVersion?.versionLabel ? currentVersion?.versionLabel : ""}`}
+                cardName={`Version ${currentVersion?.versionLabel ? currentVersion?.versionLabel : ""}`}
                 cardIcon="versionIcon"
                 versionHistory={true}
                 currentVersion={currentVersion}
diff --git a/kotsadm/web/src/components/apps/DashboardCard.jsx b/kotsadm/web/src/components/apps/DashboardCard.jsx
index f26be41fe..677fa4e9a 100644
--- a/kotsadm/web/src/components/apps/DashboardCard.jsx
+++ b/kotsadm/web/src/components/apps/DashboardCard.jsx
@@ -12,6 +12,7 @@ import url from "url";
 import AirgapUploadProgress from "../AirgapUploadProgress";
 import Loader from "../shared/Loader";
 import MountAware from "../shared/MountAware";
+import ShowLogsModal from "@src/components/modals/ShowLogsModal";
 
 import {
   dynamicallyResizeText,
@@ -26,6 +27,9 @@ export default class DashboardCard extends React.Component {
     super(props);
     this.state = {
       selectedAction: "",
+      logsLoading: false,
+      logs: null,
+      selectedTab: null,
     }
     this.cardTitleText = React.createRef();
   }
@@ -134,9 +138,98 @@ export default class DashboardCard extends React.Component {
     )
   }
 
+  renderVersionAvailable = (downstream) => {
+    if (downstream?.pendingVersions?.length > 0) {
+      return (
+        <div className="flex flex-column u-marginTop--12">
+          <p className="u-fontSize--small u-lineHeight--normal u-color--selectiveYellow u-fontWeight--medium">New version available</p>
+          <div className="flex flex1 alignItems--center u-marginTop--5">
+            <span className="u-fontSize--normal u-fontWeight--bold u-color--tundora"> {downstream?.pendingVersions[0].versionLabel} </span>
+            <Link to={`${this.props.url}/version-history`} className="card-link u-marginLeft--5"> View </Link>
+          </div>
+        </div>
+      )
+    } else {
+      return (
+        <p className="u-fontSize--small u-lineHeight--normal u-fontWeight--medium u-marginTop--12" style={{ color: "#DFDFDF" }}> No new version available </p>
+      )
+    }
+  }
+
+  hideLogsModal = () => {
+    this.setState({
+      showLogsModal: false
+    });
+  }
+
+  renderLogsTabs = () => {
+    const { logs, selectedTab } = this.state;
+    if (!logs) {
+      return null;
+    }
+    const tabs = Object.keys(logs);
+
+    return (
+      <div className="flex action-tab-bar u-marginTop--10">
+        {tabs.filter(tab => tab !== "renderError").map(tab => (
+          <div className={`tab-item blue ${tab === selectedTab && "is-active"}`} key={tab} onClick={() => this.setState({ selectedTab: tab })}>
+            {tab}
+          </div>
+        ))}
+      </div>
+    );
+  }
+
+  handleViewLogs = async (version, isFailing) => {
+    try {
+      const { app } = this.props;
+      const clusterId = app.downstreams?.length && app.downstreams[0].cluster?.id;
+
+      this.setState({ logsLoading: true, showLogsModal: true, viewLogsErrMsg: "" });
+
+      const res = await fetch(`${window.env.API_ENDPOINT}/app/${app?.slug}/cluster/${clusterId}/sequence/${version?.sequence}/downstreamoutput`, {
+        headers: {
+          "Authorization": Utilities.getToken(),
+          "Content-Type": "application/json",
+        },
+        method: "GET",
+      });
+      if (res.ok && res.status === 200) {
+        const response = await res.json();
+        let selectedTab;
+        if (isFailing) {
+          selectedTab = "applyStderr";
+        } else {
+          selectedTab = Object.keys(response.logs)[0];
+        }
+        this.setState({ logs: response.logs, selectedTab, logsLoading: false, viewLogsErrMsg: "" });
+      } else {
+        this.setState({ logsLoading: false, viewLogsErrMsg: `Failed to view logs, unexpected status code, ${res.status}` });
+      }
+    } catch (err) {
+      console.log(err)
+      this.setState({ logsLoading: false, viewLogsErrMsg: err ? `Failed to view logs: ${err.message}` : "Something went wrong, please try again." });
+    }
+  }
+
+  getCurrentVersionStatus = (version) => {
+    if (version?.status === "deployed" || version?.status === "merged" || version?.status === "pending") {
+      return <span className="u-fontSize--small u-lineHeight--normal u-color--dustyGray u-fontWeight--medium flex alignItems--center"> <span className="icon checkmark-icon u-marginRight--5" /> {Utilities.toTitleCase(version?.status).replace("_", " ")} </span>
+    } else if (version?.status === "failed") {
+      return <span className="u-fontSize--small u-lineHeight--normal u-color--red u-fontWeight--medium flex alignItems--center"> <span className="icon error-small u-marginRight--5" /> Failed <span className="u-marginLeft--5 replicated-link u-fontSize--small" onClick={() => this.handleViewLogs(version, true)}> See details </span></span>
+    } else if (version?.status === "deploying") {
+      return (
+        <span className="flex alignItems--center u-fontSize--small u-lineHeight--normal u-color--dustyGray u-fontWeight--medium">
+          <Loader className="flex alignItems--center u-marginRight--5" size="16" />
+            Deploying
+        </span>);
+    } else {
+      return <span className="u-fontSize--small u-lineHeight--normal u-color--dustyGray u-fontWeight--medium flex alignItems--center"> {Utilities.toTitleCase(version?.status).replace("_", " ")} </span>
+    }
+  }
+
   renderVersionHistoryCard = () => {
     const { app, currentVersion, downstream, checkingForUpdates, checkingForUpdateError, checkingUpdateText, errorCheckingUpdate, onCheckForUpdates, redirectToDiff, isBundleUploading } = this.props;
-    const updatesText = downstream?.pendingVersions?.length > 0 || app.isAirgap ? null : "No updates available.";
     const isUpdateAvailable = downstream?.pendingVersions?.length > 0;
 
     let checkingUpdateTextShort = checkingUpdateText;
@@ -144,9 +237,15 @@ export default class DashboardCard extends React.Component {
       checkingUpdateTextShort = checkingUpdateTextShort.slice(0, 30) + "...";
     }
 
-    let updateText = <p className="u-marginTop--10 u-fontSize--small u-color--dustyGray u-fontWeight--medium">Last checked {dayjs(app.lastUpdateCheck).fromNow()}</p>;
-    if (this.props.airgapUploadError) {
-      updateText = <p className="u-marginTop--10 u-fontSize--small u-color--chestnut u-fontWeight--medium">Error uploading bundle <span className="u-color--royalBlue u-textDecoration--underlineOnHover" onClick={this.props.viewAirgapUploadError}>View details</span></p>
+    const showOnlineUI = !app.isAirgap && !checkingForUpdates;
+    const showAirgapUI = app.isAirgap && !isBundleUploading;
+
+
+    let updateText;
+    if (showOnlineUI && app.lastUpdateCheckAt) {
+      updateText = <p className="u-marginTop--8 u-fontSize--smaller u-color--silverSand u-marginTop--8">Last checked <span className="u-fontWeight--bold">{dayjs(app.lastUpdateCheckAt).fromNow()}</span></p>;
+    } else if (this.props.airgapUploadError) {
+      updateText = <p className="u-marginTop--10 u-fontSize--small u-color--chestnut u-fontWeight--medium">Error uploading bundle <span className="u-color--royalBlue u-textDecoration--underlineOnHover" onClick={this.props.viewAirgapUploadError}>See details</span></p>
     } else if (this.props.uploadingAirgapFile) {
       updateText = (
         <AirgapUploadProgress
@@ -168,36 +267,39 @@ export default class DashboardCard extends React.Component {
       updateText = <p className="u-marginTop--10 u-fontSize--small u-color--chestnut u-fontWeight--medium">Error checking for updates, please try again</p>
     } else if (checkingForUpdates) {
       updateText = <p className="u-marginTop--10 u-fontSize--small u-color--dustyGray u-fontWeight--medium">{checkingUpdateTextShort}</p>
-    } else if (!app.lastUpdateCheck) {
+    } else if (!app.lastUpdateCheckAt) {
       updateText = null;
     }
 
-    const showAirgapUI = app.isAirgap && !isBundleUploading;
-    const showOnlineUI = !app.isAirgap && !checkingForUpdates;
-
-
     return (
-      <div>
-        <p className="u-fontWeight--bold u-fontSize--normal u-color--tundora"> {currentVersion?.status === "deployed" ? "Installed" : ""} </p>
-        <p className="u-fontSize--small u-fontWeight--medium u-color--dustyGray u-marginTop--5"> {moment(currentVersion?.createdOn).format("lll")} </p>
-
-        <p className="u-fontSize--small u-color--dustyGray u-marginTop--15"> {updatesText} </p>
+      <div className="flex1 flex-column">
+        {currentVersion?.deployedAt ?
+          <div className="flex flex-column" style={{ minHeight: "35px" }}>
+            {this.getCurrentVersionStatus(currentVersion)}
+            <p className="u-fontSize--small u-fontWeight--medium u-color--dustyGray u-marginTop--5"> {moment(currentVersion?.createdOn).format("lll")} </p>
+          </div>
+          :
+          <p className="u-fontWeight--bold u-fontSize--normal u-color--dustyGray" style={{ minHeight: "35px" }}> No version deployed </p>}
         {checkingForUpdates && !isBundleUploading
           ? <Loader className="flex justifyContent--center u-marginTop--10" size="32" />
           : showAirgapUI
             ?
-            <MountAware onMount={el => this.props.airgapUploader?.assignElement(el)}>
+            <MountAware className="u-marginTop--30" onMount={el => this.props.airgapUploader?.assignElement(el)}>
               <button className="btn secondary blue">Upload new version</button>
             </MountAware>
             : showOnlineUI ?
-              <div className="flex alignItems--center">
-                <button className="btn primary blue u-marginTop--10" onClick={isUpdateAvailable ? redirectToDiff : onCheckForUpdates}>{isUpdateAvailable ? "Show Update" : "Check for update"}</button>
-                <span className="icon settings-small-icon u-marginLeft--5 u-cursor--pointer u-marginTop--10" onClick={this.props.showUpdateCheckerModal} data-tip="Configure automatic updates"></span>
-                <ReactTooltip effect="solid" className="replicated-tooltip" />
+              <div className="flex1 flex-column" style={{ flexGrow: 1 }}>
+                {this.renderVersionAvailable(downstream)}
+                <div className="flex alignItems--center">
+                  <button className="btn primary blue u-marginTop--10" onClick={isUpdateAvailable ? redirectToDiff : onCheckForUpdates}>Check for update</button>
+                  <span className="icon settings-small-icon u-marginLeft--5 u-cursor--pointer u-marginTop--10" onClick={this.props.showUpdateCheckerModal} data-tip="Configure automatic update checks"></span>
+                  <ReactTooltip effect="solid" className="replicated-tooltip" />
+                </div>
+                {updateText}
               </div>
               : null
         }
-        {updateText}
+        {showAirgapUI && updateText}
         {checkingForUpdateError &&
           <div className="flex-column flex-auto u-marginTop--5">
             <p className="u-marginTop--10 u-fontSize--small u-color--chestnut u-fontWeight--medium">Error updating version <span className="u-color--royalBlue u-textDecoration--underlineOnHover" onClick={() => this.props.viewAirgapUpdateError(checkingUpdateText)}>View details</span></p>
@@ -237,10 +339,10 @@ export default class DashboardCard extends React.Component {
     const isSnapshotInProgress = !!snapshotInProgressApps?.find(a => a === app?.slug);
 
     return (
-      <div className={`${isSnapshotAllowed ? "small-dashboard-card" : appLicense?.licenseType === "community" ? "community-dashboard-card" : appLicense && size(appLicense) === 0 ? "grayed-dashboard-card" : "dashboard-card"} flex-column flex1 flex`}>
-        <div className="flex u-marginBottom--5">
+      <div className={`${isSnapshotAllowed ? "small-dashboard-card" : appLicense?.licenseType === "community" ? "community-dashboard-card" : appLicense && size(appLicense) === 0 ? "grayed-dashboard-card" : "dashboard-card"} flex flex1`}>
+        <div className="flex flex1 u-marginBottom--5">
           <span className={`icon ${cardIcon} u-marginRight--10`}></span>
-          <div className="flex1 justifyContent--center">
+          <div className="flex1 flex-column">
             <div className={`flex justifyContent--spaceBetween ${appLicense && size(appLicense) === 0 ? "u-marginTop--10" : ""}`}>
               <p ref={this.cardTitleText} style={{ fontSize: "20px" }} className={`flex1 u-fontWeight--bold u-fontSize--largest u-paddingRight--5 u-marginBottom--5 ${appLicense && size(appLicense) === 0 ? "u-color--doveGray" : "u-color--tundora"}`}>{cardName}</p>
             </div>
@@ -259,7 +361,7 @@ export default class DashboardCard extends React.Component {
                       !getingAppLicenseErrMsg && <span className="status-indicator completed"> Enabled </span>
                     : null
             }
-            <div className={`${isSnapshotAllowed ? "u-marginTop--8" : "u-marginTop--15"}`}>
+            <div className={`${isSnapshotAllowed || versionHistory ? "flex-auto flex-column u-marginTop--8" : "u-marginTop--15"}`}>
               <div className="flex flex1">
                 {application ?
                   this.renderApplicationCard()
@@ -279,6 +381,16 @@ export default class DashboardCard extends React.Component {
             </div>
           </div>
         </div>
+        {this.state.showLogsModal &&
+          <ShowLogsModal
+            showLogsModal={this.state.showLogsModal}
+            hideLogsModal={this.hideLogsModal}
+            viewLogsErrMsg={this.state.viewLogsErrMsg}
+            logs={this.state.logs}
+            selectedTab={this.state.selectedTab}
+            logsLoading={this.state.logsLoading}
+            renderLogsTabs={this.renderLogsTabs()}
+          />}
       </div>
     );
   }
diff --git a/kotsadm/web/src/components/modals/ShowLogsModal.jsx b/kotsadm/web/src/components/modals/ShowLogsModal.jsx
new file mode 100644
index 000000000..b87bf7951
--- /dev/null
+++ b/kotsadm/web/src/components/modals/ShowLogsModal.jsx
@@ -0,0 +1,58 @@
+import React from "react";
+import Modal from "react-modal";
+import MonacoEditor from "react-monaco-editor";
+import Loader from "../shared/Loader";
+
+export default function ShowLogsModal(props) {
+  const { showLogsModal, hideLogsModal, viewLogsErrMsg, logs, selectedTab, logsLoading, renderLogsTabs } = props;
+
+  return (
+    <Modal
+    isOpen={showLogsModal}
+    onRequestClose={hideLogsModal}
+    shouldReturnFocusAfterClose={false}
+    contentLabel="View logs"
+    ariaHideApp={false}
+    className="Modal logs-modal"
+  >
+    <div className="Modal-body flex flex1">
+      {viewLogsErrMsg ?
+        <div class="flex1 flex-column justifyContent--center alignItems--center">
+          <span className="icon redWarningIcon" />
+          <p className="u-color--chestnut u-fontSize--normal u-fontWeight--medium u-lineHeight--normal u-marginTop--10">{viewLogsErrMsg}</p>
+        </div>
+        :
+        !logs || !selectedTab || logsLoading ? (
+          <div className="flex-column flex1 alignItems--center justifyContent--center">
+            <Loader size="60" />
+          </div>
+        ) : (
+            <div className="flex-column flex1">
+              <div className="flex-column flex1">
+                {!logs.renderError && renderLogsTabs}
+                <div className="flex-column flex1 u-border--gray monaco-editor-wrapper">
+                  <MonacoEditor
+                    language="json"
+                    value={logs.renderError || logs[selectedTab]}
+                    height="100%"
+                    width="100%"
+                    options={{
+                      readOnly: true,
+                      contextmenu: false,
+                      minimap: {
+                        enabled: false
+                      },
+                      scrollBeyondLastLine: false,
+                    }}
+                  />
+                </div>
+              </div>
+              <div className="u-marginTop--20 flex">
+                <button type="button" className="btn primary" onClick={hideLogsModal}>Ok, got it!</button>
+              </div>
+            </div>
+          )}
+    </div>
+  </Modal>
+  );
+}
\ No newline at end of file
diff --git a/kotsadm/web/src/components/modals/UpdateCheckerModal.jsx b/kotsadm/web/src/components/modals/UpdateCheckerModal.jsx
index 314f420bb..9b442f63d 100644
--- a/kotsadm/web/src/components/modals/UpdateCheckerModal.jsx
+++ b/kotsadm/web/src/components/modals/UpdateCheckerModal.jsx
@@ -128,7 +128,7 @@ export default class UpdateCheckerModal extends React.Component {
         className="Modal SmallSize"
       >
         <div className="u-position--relative flex-column u-padding--20">
-          <span className="u-fontSize--largest u-fontWeight--bold u-color--tuna u-marginBottom--15">Configure automatic updates</span>
+          <span className="u-fontSize--largest u-fontWeight--bold u-color--tuna u-marginBottom--15">Configure automatic update checks</span>
           {gitopsEnabled ? 
             <p className="u-fontSize--normal u-lineHeight--normal u-color--dustyGray u-marginBottom--20">
               Configure how often you would like to automatically check for updates.<br/>A commit will be made if an update was found.
diff --git a/kotsadm/web/src/images/main_spritesheet.svg b/kotsadm/web/src/images/main_spritesheet.svg
index b85ad312c..fd5f8129d 100644
--- a/kotsadm/web/src/images/main_spritesheet.svg
+++ b/kotsadm/web/src/images/main_spritesheet.svg
@@ -182,7 +182,6 @@
 <rect id="Rectangle-2" x="92" y="247" width="24" height="4" rx="2" fill="white" stroke="white"/>
 </g>
 </g>
-<g id="Group-13_3">
 <g id="Group-2_4">
 <path id="Oval-7_3" fill-rule="evenodd" clip-rule="evenodd" d="M146 269C157.046 269 166 260.046 166 249C166 237.954 157.046 229 146 229C134.954 229 126 237.954 126 249C126 260.046 134.954 269 146 269Z" fill="#9B9B9B"/>
 <g id="?">
@@ -199,12 +198,11 @@
 </g>
 </g>
 </g>
-</g>
 <g id="Group 58">
 <path id="Triangle" fill-rule="evenodd" clip-rule="evenodd" d="M143.377 273.192C144.52 271.133 147.48 271.133 148.623 273.192L166.026 304.544C167.136 306.544 165.69 309 163.403 309H128.597C126.31 309 124.864 306.544 125.974 304.544L143.377 273.192Z" fill="#EBB55C"/>
 <path id="!_2" fill-rule="evenodd" clip-rule="evenodd" d="M146.879 297.006L147.873 288.378V283H144.102V288.378L145.07 297.006H146.879ZM148 298.686H144V303H148V298.686Z" fill="white"/>
 </g>
-<g id="Group-13_4">
+<g id="Group-13_3">
 <g id="Group-2_6">
 <path id="Oval-7_5" fill-rule="evenodd" clip-rule="evenodd" d="M72 287C76.4183 287 80 283.418 80 279C80 274.582 76.4183 271 72 271C67.5817 271 64 274.582 64 279C64 283.418 67.5817 287 72 287Z" fill="#46A9FF"/>
 <rect id="Rectangle-2_2" x="67.25" y="278.65" width="9.5" height="1.1" rx="0.55" fill="white" stroke="white" stroke-width="0.5"/>
@@ -511,7 +509,7 @@
 </g>
 </g>
 </g>
-<g id="Group-13_5">
+<g id="Group-13_4">
 <path id="Oval-7_7" fill-rule="evenodd" clip-rule="evenodd" d="M26 287C30.4183 287 34 283.418 34 279C34 274.582 30.4183 271 26 271C21.5817 271 18 274.582 18 279C18 283.418 21.5817 287 26 287Z" fill="#EBB55C"/>
 <path id="!_3" fill-rule="evenodd" clip-rule="evenodd" d="M26.3515 280.602L26.749 277.151V275H25.2407V277.151L25.628 280.602H26.3515ZM26.8 281.275H25.2V283H26.8V281.275Z" fill="white"/>
 </g>
@@ -1092,7 +1090,7 @@
 <path id="Rectangle 2 Copy 3" d="M137.082 148.031C137.082 147.693 136.808 147.419 136.469 147.419C136.131 147.419 135.857 147.693 135.857 148.031V152.805C135.857 153.143 136.131 153.417 136.469 153.417C136.808 153.417 137.082 153.143 137.082 152.805V148.031Z" fill="#326DE6"/>
 </g>
 <g id="Group 26_3">
-<path id="gear" d="M141.918 131.124C141.817 130.788 141.828 130.426 141.954 130.11L142.382 129.077C142.45 128.971 142.43 128.822 142.336 128.728L141.272 127.664C141.188 127.58 141.036 127.562 140.859 127.662L139.884 128.048C139.114 128.349 138.259 127.984 137.954 127.24L137.547 126.312L137.514 126.203C137.491 126.085 137.387 126 137.258 126H135.742C135.614 126 135.506 126.085 135.485 126.186L135.455 126.285L135.043 127.246C134.71 127.977 133.874 128.324 133.11 128.046L132.16 127.661L132.063 127.608C131.971 127.545 131.831 127.561 131.728 127.664L130.664 128.728C130.58 128.812 130.562 128.964 130.662 129.141L131.048 130.116C131.349 130.886 130.984 131.741 130.24 132.046L129.312 132.453L129.203 132.486C129.085 132.509 129 132.613 129 132.742V134.258C129 134.386 129.085 134.494 129.186 134.515L129.285 134.545L130.246 134.957C130.977 135.29 131.324 136.126 131.046 136.89L130.618 137.923C130.543 138.04 130.557 138.166 130.664 138.272L131.728 139.336C131.812 139.42 131.964 139.438 132.141 139.338L133.116 138.952C133.886 138.651 134.741 139.016 135.046 139.76L135.453 140.688L135.486 140.797C135.509 140.915 135.613 141 135.742 141H137.258C137.386 141 137.494 140.915 137.515 140.814L137.545 140.715L137.957 139.754C138.29 139.023 139.126 138.676 139.89 138.954L140.84 139.339L140.937 139.392C141.029 139.455 141.169 139.439 141.272 139.336L142.336 138.272C142.42 138.188 142.438 138.036 142.338 137.859L141.952 136.884C141.651 136.114 142.016 135.259 142.76 134.954L143.688 134.547L143.797 134.514C143.915 134.491 144 134.387 144 134.258V132.742C144 132.613 143.915 132.509 143.797 132.486L143.692 132.455L142.753 132.052C142.413 131.916 142.14 131.651 141.971 131.313L141.918 131.208V131.124ZM136.489 137.27C134.402 137.27 132.707 135.575 132.707 133.489C132.707 131.402 134.402 129.707 136.489 129.707C138.575 129.707 140.27 131.402 140.27 133.489C140.27 135.575 138.575 137.27 136.489 137.27Z" stroke="#326DE6"/>
+<path id="gear" d="M142.976 131.495C142.791 131.421 142.628 131.274 142.518 131.066V131.037L142.493 130.953C142.429 130.738 142.439 130.517 142.51 130.336L142.922 129.342C143.111 128.989 143.022 128.565 142.76 128.303L141.697 127.24C141.37 126.914 140.923 126.956 140.607 127.117L139.666 127.489C139.666 127.489 139.665 127.489 139.665 127.489C139.194 127.673 138.687 127.446 138.51 127.013L138.507 127.006L138.504 126.999L138.11 126.102L138.094 126.05C137.999 125.653 137.642 125.4 137.258 125.4H135.742C135.389 125.4 135.007 125.626 134.906 126.029L134.891 126.078L134.494 127.003C134.293 127.438 133.795 127.651 133.324 127.485L132.416 127.117L132.369 127.092C131.996 126.86 131.558 126.985 131.303 127.24L130.24 128.303C129.914 128.63 129.956 129.077 130.117 129.393L130.489 130.334C130.489 130.334 130.489 130.335 130.489 130.335C130.673 130.806 130.446 131.313 130.013 131.49L130.006 131.493L129.999 131.496L129.102 131.89L129.05 131.906C128.653 132.001 128.4 132.358 128.4 132.742V134.258C128.4 134.611 128.626 134.993 129.029 135.094L129.078 135.109L130.003 135.506C130.437 135.706 130.651 136.204 130.486 136.674L130.079 137.656C129.998 137.805 129.953 137.984 129.978 138.178C130.005 138.395 130.113 138.57 130.24 138.697L131.303 139.76L131.728 139.336L131.303 139.76C131.63 140.086 132.077 140.044 132.393 139.883L133.334 139.511C133.334 139.511 133.335 139.511 133.335 139.511C133.806 139.327 134.313 139.554 134.49 139.987L134.493 139.994L134.496 140.001L134.89 140.898L134.906 140.95C135.001 141.347 135.358 141.6 135.742 141.6H137.258C137.611 141.6 137.993 141.374 138.094 140.971L138.109 140.922L138.506 139.997C138.707 139.562 139.205 139.349 139.676 139.515L140.584 139.883L140.631 139.908C141.004 140.14 141.442 140.015 141.697 139.76L142.76 138.697C143.086 138.37 143.044 137.923 142.883 137.607L142.511 136.666C142.511 136.665 142.511 136.665 142.511 136.665C142.327 136.194 142.554 135.687 142.987 135.51L142.994 135.507L143.001 135.504L143.898 135.11L143.95 135.094C144.347 134.999 144.6 134.642 144.6 134.258V132.742C144.6 132.357 144.346 132 143.949 131.906L143.898 131.89L142.989 131.5L142.982 131.498L142.976 131.495ZM136.489 136.67C134.733 136.67 133.307 135.244 133.307 133.489C133.307 131.733 134.733 130.307 136.489 130.307C138.244 130.307 139.67 131.733 139.67 133.489C139.67 135.244 138.244 136.67 136.489 136.67Z" stroke="#326DE6" stroke-width="1.2"/>
 </g>
 <g id="Group 2_12">
 <path id="Oval_37" d="M178 380C178 390.493 169.493 399 159 399C148.507 399 140 390.493 140 380C140 369.507 148.507 361 159 361C169.493 361 178 369.507 178 380Z" fill="white" stroke="#254E58" stroke-width="2"/>
@@ -1172,6 +1170,17 @@
 <path id="Shape_39" d="M204.293 306.779L207.037 309.481L204.293 306.779ZM204.293 306.779L202.508 308.536L205.997 310.499L205.752 310.935L205.997 310.499C205.998 310.5 205.999 310.5 206.001 310.5C206.002 310.5 206.003 310.5 206.003 310.499C206.003 310.499 206.003 310.499 206.003 310.499L204.293 306.779ZM211.497 295.5L211.146 295.144L211.497 295.5C211.498 295.5 211.498 295.5 211.497 295.5C211.497 295.5 211.497 295.5 211.497 295.5C211.498 295.5 211.499 295.5 211.5 295.5C211.502 295.5 211.503 295.5 211.503 295.5C211.503 295.5 211.503 295.5 211.503 295.5C211.502 295.5 211.503 295.5 211.503 295.5L218.494 302.385L213.957 306.852L206.963 299.965L211.497 295.5ZM205.285 304.959L205.986 301.8L212.088 307.807L208.879 308.498L205.285 304.959Z" fill="white" stroke="#326DE6"/>
 <path id="Vector 2" d="M204 307.5L203 308.5L206 310L206.5 309.5L204.5 307L204 307.5Z" fill="#326DE6"/>
 </g>
+<g id="Group 26_4">
+<path id="Rectangle_19" d="M154.865 747.6H171.135C171.834 747.6 172.4 748.166 172.4 748.865V765.135C172.4 765.834 171.834 766.4 171.135 766.4H154.865C154.166 766.4 153.6 765.834 153.6 765.135V748.865C153.6 748.166 154.166 747.6 154.865 747.6Z" fill="white" stroke="#326DE6" stroke-width="1.2"/>
+<g id="Group 127">
+<path id="Path 13" d="M163.129 752.172H168.629" stroke="#326DE6" stroke-width="1.2" stroke-linecap="round"/>
+<path id="Path-4_7" d="M157.129 762.172L158.118 763.132C158.179 763.188 158.274 763.184 158.33 763.123C158.333 763.12 158.335 763.117 158.338 763.114L160.129 760.672" stroke="#326DE6" stroke-width="1.2" stroke-linecap="round"/>
+<path id="Path-4_8" d="M157.129 757.172L158.118 758.132C158.179 758.188 158.274 758.184 158.33 758.123C158.333 758.12 158.335 758.117 158.338 758.114L160.129 755.672" stroke="#326DE6" stroke-width="1.2" stroke-linecap="round"/>
+<path id="Path-4_9" d="M157.129 752.172L158.118 753.132C158.179 753.188 158.274 753.184 158.33 753.123C158.333 753.12 158.335 753.117 158.338 753.114L160.129 750.672" stroke="#326DE6" stroke-width="1.2" stroke-linecap="round"/>
+<path id="Path 13 Copy 2" d="M163.129 762.172L168.629 762.172" stroke="#326DE6" stroke-width="1.2" stroke-linecap="round"/>
+<path id="Path 13 Copy 3" d="M163.129 757.172L168.629 757.172" stroke="#326DE6" stroke-width="1.2" stroke-linecap="round"/>
+</g>
+</g>
 <path id="gear copy_2" d="M215.465 284.683L214.634 284.327C214.436 284.248 214.277 284.089 214.178 283.891V283.851C214.099 283.634 214.099 283.396 214.178 283.198L214.515 282.366C214.693 282.089 214.634 281.733 214.416 281.515L213.485 280.584C213.267 280.366 212.911 280.327 212.653 280.485L211.802 280.822C211.347 281 210.851 280.782 210.673 280.347L210.317 279.535C210.257 279.218 209.98 279 209.663 279H208.337C208.02 279 207.743 279.218 207.683 279.515L207.327 280.347C207.129 280.782 206.634 280.98 206.198 280.822L205.366 280.485C205.109 280.307 204.752 280.347 204.515 280.584L203.584 281.515C203.366 281.733 203.327 282.089 203.485 282.347L203.822 283.198C204 283.653 203.782 284.149 203.347 284.327L202.535 284.683C202.218 284.743 202 285.02 202 285.337V286.663C202 286.98 202.218 287.257 202.515 287.317L203.347 287.673C203.782 287.871 203.98 288.366 203.822 288.802L203.485 289.634C203.307 289.911 203.347 290.248 203.584 290.485L204.515 291.416C204.733 291.634 205.089 291.673 205.347 291.515L206.198 291.178C206.653 291 207.149 291.218 207.327 291.653L207.683 292.465C207.743 292.782 208.02 293 208.337 293H209.663C209.98 293 210.257 292.782 210.317 292.485L210.673 291.653C210.871 291.218 211.366 291.02 211.802 291.178L212.634 291.515C212.891 291.693 213.248 291.653 213.485 291.416L214.416 290.485C214.634 290.267 214.673 289.911 214.515 289.653L214.178 288.802C214 288.347 214.218 287.851 214.653 287.673L215.465 287.317C215.782 287.257 216 286.98 216 286.663V285.337C216 285.02 215.782 284.743 215.465 284.683ZM208.99 288.861C207.406 288.861 206.119 287.574 206.119 285.99C206.119 284.406 207.406 283.119 208.99 283.119C210.574 283.119 211.861 284.406 211.861 285.99C211.861 287.574 210.574 288.861 208.99 288.861Z" fill="#9B9B9B"/>
 <g id="Group-7_8">
 <path id="Path-8_3" d="M114.973 298.452L118.998 294.479L114.936 290.457" stroke="#9B9B9B" stroke-width="2" stroke-linecap="round"/>
@@ -1208,8 +1217,8 @@
 <path id="T" d="M176.943 628.962V634.2H178.356V628.962H180.282V627.774H175.017V628.962H176.943Z" fill="#4A4A4A"/>
 </g>
 <g id="Group 95">
-<path id="Rectangle_19" d="M70.7652 440.047H71.2701L71.2652 439.542L71.2612 439.135L71.2608 439.087L71.2514 439.041L69.1012 428.378C69.0497 428.123 68.9114 427.903 68.7216 427.747C68.5317 427.592 68.2888 427.5 68.0284 427.5H57.7391C57.4787 427.5 57.2358 427.592 57.0459 427.747C56.856 427.903 56.7178 428.123 56.6663 428.378L54.5465 438.89L54.5366 438.939L54.5366 438.989L54.5366 439.547L54.5367 440.047H55.0366H70.7652Z" fill="white" stroke="#326DE6"/>
-<path id="Rectangle_20" d="M70.195 443.5H70.1951C70.5177 443.5 70.7975 443.352 70.989 443.137C71.1797 442.922 71.2895 442.636 71.2895 442.332L71.2895 442.329L71.2735 439.275H70.7735L71.2735 439.273L71.2735 439.274C71.2732 439.093 71.2078 438.919 71.0891 438.786C70.9693 438.651 70.7885 438.553 70.5754 438.553L55.058 438.553L54.8336 438.553L54.6844 438.72C54.5654 438.854 54.5 439.029 54.5 439.21V442.329C54.5 442.633 54.6098 442.919 54.8005 443.134C54.992 443.349 55.2718 443.497 55.5943 443.497C55.5943 443.497 55.5943 443.497 55.5944 443.497L70.195 443.5Z" fill="white" stroke="#326DE6"/>
+<path id="Rectangle_20" d="M70.7652 440.047H71.2701L71.2652 439.542L71.2612 439.135L71.2608 439.087L71.2514 439.041L69.1012 428.378C69.0497 428.123 68.9114 427.903 68.7216 427.747C68.5317 427.592 68.2888 427.5 68.0284 427.5H57.7391C57.4787 427.5 57.2358 427.592 57.0459 427.747C56.856 427.903 56.7178 428.123 56.6663 428.378L54.5465 438.89L54.5366 438.939L54.5366 438.989L54.5366 439.547L54.5367 440.047H55.0366H70.7652Z" fill="white" stroke="#326DE6"/>
+<path id="Rectangle_21" d="M70.195 443.5H70.1951C70.5177 443.5 70.7975 443.352 70.989 443.137C71.1797 442.922 71.2895 442.636 71.2895 442.332L71.2895 442.329L71.2735 439.275H70.7735L71.2735 439.273L71.2735 439.274C71.2732 439.093 71.2078 438.919 71.0891 438.786C70.9693 438.651 70.7885 438.553 70.5754 438.553L55.058 438.553L54.8336 438.553L54.6844 438.72C54.5654 438.854 54.5 439.029 54.5 439.21V442.329C54.5 442.633 54.6098 442.919 54.8005 443.134C54.992 443.349 55.2718 443.497 55.5943 443.497C55.5943 443.497 55.5943 443.497 55.5944 443.497L70.195 443.5Z" fill="white" stroke="#326DE6"/>
 <path id="Oval_42" d="M57.3368 441.744C57.7729 441.744 58.1263 441.39 58.1263 440.954C58.1263 440.518 57.7729 440.165 57.3368 440.165C56.9008 440.165 56.5474 440.518 56.5474 440.954C56.5474 441.39 56.9008 441.744 57.3368 441.744Z" fill="#326DE6"/>
 </g>
 <path id="Storage class_2" d="M196 229C191.614 229 188.073 230.261 188 231.836C188.001 231.852 188.001 231.869 188 231.885C188 232.067 188.11 232.301 188.469 232.59C188.828 232.879 189.408 233.178 190.141 233.426C191.606 233.924 193.689 234.246 196 234.246C198.311 234.246 200.394 233.924 201.859 233.426C202.592 233.178 203.172 232.879 203.531 232.59C203.89 232.301 204 232.067 204 231.885C204 231.874 204 231.863 204 231.852C203.951 230.269 200.401 229 196 229ZM188 233.533V237.344C188.001 237.361 188.001 237.377 188 237.393C188 237.575 188.11 237.809 188.469 238.098C188.828 238.387 189.408 238.686 190.141 238.934C191.606 239.432 193.689 239.754 196 239.754C198.311 239.754 200.394 239.432 201.859 238.934C202.592 238.686 203.172 238.387 203.531 238.098C203.89 237.809 204 237.575 204 237.393V237.361V233.533C203.519 233.886 202.903 234.178 202.172 234.426C200.564 234.972 198.398 235.295 196 235.295C193.602 235.295 191.436 234.972 189.828 234.426C189.097 234.178 188.481 233.886 188 233.533ZM188 239.041V242.59C188.001 242.607 188.001 242.623 188 242.639C188 242.821 188.11 243.055 188.469 243.344C188.828 243.633 189.408 243.932 190.141 244.18C191.606 244.678 193.689 245 196 245C198.311 245 200.394 244.678 201.859 244.18C202.592 243.932 203.172 243.633 203.531 243.344C203.89 243.055 204 242.821 204 242.639V242.607V239.041C203.519 239.394 202.903 239.686 202.172 239.934C200.564 240.48 198.398 240.803 196 240.803C193.602 240.803 191.436 240.48 189.828 239.934C189.097 239.686 188.481 239.394 188 239.041Z" fill="#44BBB4"/>
@@ -1273,6 +1282,46 @@
 <path id="Vector_24" fill-rule="evenodd" clip-rule="evenodd" d="M229.351 562.602L229.749 559.151V557H228.241V559.151L228.628 562.602H229.351ZM229.8 563.274H228.2V565H229.8V563.274Z" fill="white"/>
 </g>
 </g>
+<g id="Group 29">
+<g id="Group 27">
+<path id="Rectangle Copy" d="M141.222 749.42L127.376 747.965C126.499 747.873 125.714 748.509 125.622 749.385L124.167 763.231C124.075 764.107 124.71 764.892 125.587 764.985L139.433 766.44C140.309 766.532 141.094 765.896 141.187 765.02L142.642 751.174C142.734 750.297 142.098 749.512 141.222 749.42Z" fill="white" stroke="#326DE6" stroke-width="1.2"/>
+<g id="Group 26_5">
+<path id="Rectangle_22" d="M142.624 747.167L128.778 748.622C127.902 748.714 127.266 749.499 127.358 750.376L128.813 764.222C128.906 765.098 129.691 765.734 130.567 765.642L144.413 764.187C145.29 764.094 145.925 763.309 145.833 762.433L144.378 748.587C144.286 747.71 143.501 747.075 142.624 747.167Z" fill="white" stroke="#326DE6" stroke-width="1.2"/>
+<path id="Path 13_2" d="M131.061 753.031L141.378 751.947" stroke="#326DE6" stroke-width="1.2" stroke-linecap="round"/>
+<path id="Path 13 Copy" d="M131.394 756.55L141.711 755.466" stroke="#326DE6" stroke-width="1.2" stroke-linecap="round"/>
+<path id="Path 13 Copy 2_2" d="M131.728 760.451L142.045 759.367" stroke="#326DE6" stroke-width="1.2" stroke-linecap="round"/>
+</g>
+</g>
+</g>
+<g id="Group 137">
+<g id="Group 122">
+<rect id="Rectangle 72" width="7" height="1.2" rx="0.6" transform="matrix(-1 0 0 1 185.002 765.8)" fill="#326DE6"/>
+<rect id="Rectangle 73" width="9" height="1.2" rx="0.6" transform="matrix(-1 0 0 1 187.002 762.6)" fill="#326DE6"/>
+<rect id="Rectangle 74" width="10" height="1.2" rx="0.6" transform="matrix(-1 0 0 1 188.002 759.401)" fill="#326DE6"/>
+<rect id="Rectangle 75" width="11.5" height="1.2" rx="0.6" transform="matrix(-1 0 0 1 189.5 756.2)" fill="#326DE6"/>
+<rect id="Rectangle 76" width="21" height="1.2" rx="0.6" transform="matrix(-1 0 0 1 199.002 753)" fill="#326DE6"/>
+<g id="Group 120">
+<rect id="Rectangle 73_2" x="187.46" y="766.388" width="5.75238" height="1.2" rx="0.6" transform="rotate(-45 187.46 766.388)" fill="#326DE6"/>
+<circle id="Ellipse 8" cx="194.332" cy="760.401" r="3.9" stroke="#326DE6" stroke-width="1.2"/>
+</g>
+</g>
+</g>
+<g id="Group 136">
+<g id="Group 2_13">
+<path id="Rectangle 29_10" d="M216.988 752.25L216.993 752.256L216.998 752.261L218.942 754.311C218.987 754.359 219.013 754.422 219.013 754.488L218.995 767.717C218.994 768.095 218.687 768.402 218.308 768.402H205.287C204.907 768.402 204.6 768.095 204.6 767.716V750.29C204.6 749.909 204.911 749.6 205.292 749.603L214.56 749.677C214.632 749.678 214.701 749.709 214.749 749.763L216.988 752.25Z" fill="white" stroke="#326DE6" stroke-width="1.2"/>
+</g>
+<path id="gear copy_3" d="M215.963 758.605L215.428 758.375C215.301 758.325 215.199 758.223 215.135 758.095V758.07C215.084 757.93 215.084 757.777 215.135 757.65L215.352 757.115C215.466 756.937 215.428 756.708 215.288 756.568L214.69 755.97C214.55 755.83 214.321 755.804 214.155 755.906L213.608 756.122C213.315 756.237 212.997 756.097 212.882 755.817L212.653 755.295C212.615 755.091 212.437 754.951 212.233 754.951H211.38C211.176 754.951 210.998 755.091 210.96 755.282L210.731 755.817C210.603 756.097 210.285 756.224 210.005 756.122L209.47 755.906C209.305 755.791 209.076 755.817 208.923 755.97L208.325 756.568C208.185 756.708 208.159 756.937 208.261 757.103L208.478 757.65C208.592 757.943 208.452 758.261 208.172 758.375L207.65 758.605C207.446 758.643 207.306 758.821 207.306 759.025V759.878C207.306 760.081 207.446 760.26 207.637 760.298L208.172 760.527C208.452 760.654 208.579 760.972 208.478 761.252L208.261 761.787C208.147 761.965 208.172 762.182 208.325 762.334L208.923 762.933C209.063 763.073 209.292 763.098 209.458 762.996L210.005 762.78C210.298 762.665 210.616 762.805 210.731 763.086L210.96 763.607C210.998 763.811 211.176 763.951 211.38 763.951H212.233C212.437 763.951 212.615 763.811 212.653 763.62L212.882 763.086C213.009 762.805 213.328 762.678 213.608 762.78L214.142 762.996C214.308 763.111 214.537 763.086 214.69 762.933L215.288 762.334C215.428 762.194 215.453 761.965 215.352 761.8L215.135 761.252C215.021 760.96 215.161 760.641 215.441 760.527L215.963 760.298C216.166 760.26 216.306 760.081 216.306 759.878V759.025C216.306 758.821 216.166 758.643 215.963 758.605ZM211.8 761.291C210.782 761.291 209.954 760.463 209.954 759.445C209.954 758.426 210.782 757.599 211.8 757.599C212.818 757.599 213.646 758.426 213.646 759.445C213.646 760.463 212.818 761.291 211.8 761.291Z" stroke="#326DE6" stroke-width="1.2"/>
+</g>
+<path id="gear_2" d="M237.976 758.495C237.791 758.421 237.628 758.274 237.518 758.066V758.037L237.493 757.953C237.429 757.738 237.439 757.517 237.51 757.336L237.922 756.342C238.111 755.989 238.022 755.565 237.76 755.303L236.697 754.24C236.37 753.914 235.923 753.956 235.607 754.117L234.666 754.489C234.666 754.489 234.665 754.489 234.665 754.489C234.194 754.673 233.687 754.446 233.51 754.013L233.507 754.006L233.504 753.999L233.11 753.102L233.094 753.05C232.999 752.653 232.642 752.4 232.258 752.4H230.742C230.389 752.4 230.007 752.626 229.906 753.029L229.891 753.078L229.494 754.003C229.293 754.438 228.795 754.651 228.324 754.485L227.416 754.117L227.369 754.092C226.996 753.86 226.558 753.985 226.303 754.24L225.24 755.303C224.914 755.63 224.956 756.077 225.117 756.393L225.489 757.334C225.489 757.334 225.489 757.335 225.489 757.335C225.673 757.806 225.446 758.313 225.013 758.49L225.006 758.493L224.999 758.496L224.102 758.89L224.05 758.906C223.653 759.001 223.4 759.358 223.4 759.742V761.258C223.4 761.611 223.626 761.993 224.029 762.094L224.078 762.109L225.003 762.506C225.437 762.706 225.651 763.204 225.486 763.674L225.079 764.656C224.998 764.805 224.953 764.984 224.978 765.178C225.005 765.395 225.113 765.57 225.24 765.697L226.303 766.76C226.63 767.086 227.077 767.044 227.393 766.883L228.334 766.511C228.334 766.511 228.335 766.511 228.335 766.511C228.806 766.327 229.313 766.554 229.49 766.987L229.493 766.994L229.496 767.001L229.89 767.898L229.906 767.95C230.001 768.347 230.358 768.6 230.742 768.6H232.258C232.611 768.6 232.993 768.374 233.094 767.971L233.109 767.922L233.506 766.997C233.707 766.562 234.205 766.349 234.676 766.515L235.584 766.883L235.631 766.908C236.004 767.14 236.442 767.015 236.697 766.76L237.76 765.697C238.086 765.37 238.044 764.923 237.883 764.607L237.511 763.666C237.511 763.665 237.511 763.665 237.511 763.665C237.327 763.194 237.554 762.687 237.987 762.51L237.994 762.507L238.001 762.504L238.898 762.11L238.95 762.094C239.347 761.999 239.6 761.642 239.6 761.258V759.742C239.6 759.357 239.346 759 238.949 758.906L238.898 758.89L237.989 758.5L237.982 758.498L237.976 758.495ZM231.489 763.67C229.733 763.67 228.307 762.244 228.307 760.489C228.307 758.733 229.733 757.307 231.489 757.307C233.244 757.307 234.67 758.733 234.67 760.489C234.67 762.244 233.244 763.67 231.489 763.67Z" stroke="#326DE6" stroke-width="1.2"/>
+<g id="Group 27_2">
+<path id="Rectangle Copy_2" d="M231.926 726.957L220.73 725.78C220.021 725.706 219.386 726.22 219.312 726.928L218.135 738.125C218.06 738.834 218.575 739.468 219.283 739.543L230.48 740.72C231.188 740.794 231.823 740.28 231.898 739.571L233.075 728.375C233.149 727.666 232.635 727.031 231.926 726.957Z" fill="white" stroke="#326DE6" stroke-width="1.2"/>
+<g id="Group 26_6">
+<path id="Rectangle_23" d="M233.06 725.135L221.864 726.312C221.155 726.386 220.641 727.021 220.715 727.73L221.892 738.926C221.967 739.635 222.602 740.149 223.31 740.075L234.507 738.898C235.215 738.823 235.73 738.188 235.655 737.48L234.478 726.283C234.404 725.575 233.769 725.06 233.06 725.135Z" fill="white" stroke="#326DE6" stroke-width="1.2"/>
+<path id="Path 13_3" d="M223.709 729.877L232.052 729" stroke="#326DE6" stroke-width="1.2" stroke-linecap="round"/>
+<path id="Path 13 Copy_2" d="M223.979 732.722L232.322 731.846" stroke="#326DE6" stroke-width="1.2" stroke-linecap="round"/>
+<path id="Path 13 Copy 2_3" d="M224.249 735.877L232.592 735" stroke="#326DE6" stroke-width="1.2" stroke-linecap="round"/>
+</g>
+</g>
 </g>
 <defs>
 <filter id="filter0_d" x="145.363" y="626.099" width="15.4427" height="14.0858" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
diff --git a/kotsadm/web/src/scss/components/apps/AppVersionHistory.scss b/kotsadm/web/src/scss/components/apps/AppVersionHistory.scss
index a78e01085..90b8726b9 100644
--- a/kotsadm/web/src/scss/components/apps/AppVersionHistory.scss
+++ b/kotsadm/web/src/scss/components/apps/AppVersionHistory.scss
@@ -47,11 +47,11 @@ $cell-width: 140px;
 }
 
 .upstream-version-box-wrapper {
-  background-color: #F5F8FC;
-  border-radius: 4px;
+  background: #F8F8F8;
+  border-radius: 6px;
   padding: 15px;
   width: 100%;
-  max-width: 560px;
+  max-width: 663px;
 
   .watch-icon {
     width: 48px;
diff --git a/kotsadm/web/src/scss/utilities/icons.scss b/kotsadm/web/src/scss/utilities/icons.scss
index 93cb3aabf..2bded8fbd 100644
--- a/kotsadm/web/src/scss/utilities/icons.scss
+++ b/kotsadm/web/src/scss/utilities/icons.scss
@@ -224,9 +224,34 @@
   height: 14px;
 }
 .icon.settings-small-icon {
-  background-position: -202px -279px;
-  width: 14px;
-  height: 14px;
+  background-position: -223px -752px;
+  width: 17px;
+  height: 17px;
+}
+.icon.releaseNotes-small--icon {
+  background-position: -218px -725px;
+  width: 18px;
+  height: 16px;
+}
+.icon.releaseNotes--icon {
+  background-position: -124px -747px;
+  width: 22px;
+  height: 20px;
+}
+.icon.preflightChecks--icon {
+  background-position: -153px -747px;
+  width: 20px;
+  height: 20px;
+}
+.icon.deployLogs--icon {
+  background-position: -178px -753px;
+  width: 21px;
+  height: 15px;
+}
+.icon.config--icon {
+  background-position: -204px -749px;
+  width: 16px;
+  height: 20px;
 }
 
 /* Analyzer icons */
diff --git a/pkg/docker/registry/registry.go b/pkg/docker/registry/registry.go
index 443b4c874..09245f475 100644
--- a/pkg/docker/registry/registry.go
+++ b/pkg/docker/registry/registry.go
@@ -64,7 +64,7 @@ func (r *RegistryProxyInfo) ToSlice() []string {
 	}
 }
 
-func PullSecretForRegistries(registries []string, username, password string, kuberneteNamespace string) (*corev1.Secret, error) {
+func PullSecretForRegistries(registries []string, username, password string, namespace string) (*corev1.Secret, error) {
 	dockercfgAuth := DockercfgAuth{
 		Auth: base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", username, password))),
 	}
@@ -89,7 +89,7 @@ func PullSecretForRegistries(registries []string, username, password string, kub
 		},
 		ObjectMeta: metav1.ObjectMeta{
 			Name:      "kotsadm-replicated-registry",
-			Namespace: kuberneteNamespace,
+			Namespace: namespace,
 		},
 		Type: corev1.SecretTypeDockerConfigJson,
 		Data: map[string][]byte{
diff --git a/pkg/k8sdoc/types.go b/pkg/k8sdoc/types.go
index 036cab88d..fdd059c9e 100644
--- a/pkg/k8sdoc/types.go
+++ b/pkg/k8sdoc/types.go
@@ -2,7 +2,6 @@ package k8sdoc
 
 import (
 	"github.com/pkg/errors"
-	troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
 	"gopkg.in/yaml.v2"
 	corev1 "k8s.io/api/core/v1"
 )
@@ -12,10 +11,6 @@ type K8sDoc interface {
 	ListImages() []string
 }
 
-var _ K8sDoc = (*Doc)(nil)
-var _ K8sDoc = (*PodDoc)(nil)
-var _ K8sDoc = (*SupportBundleDoc)(nil)
-
 type Doc struct {
 	APIVersion string   `yaml:"apiVersion"`
 	Kind       string   `yaml:"kind"`
@@ -30,13 +25,6 @@ type PodDoc struct {
 	Spec       PodSpec  `yaml:"spec"`
 }
 
-type SupportBundleDoc struct {
-	APIVersion string                                `yaml:"apiVersion"`
-	Kind       string                                `yaml:"kind"`
-	Metadata   Metadata                              `yaml:"metadata"`
-	Spec       troubleshootv1beta2.SupportBundleSpec `yaml:"spec"`
-}
-
 type Metadata struct {
 	Name      string            `yaml:"name"`
 	Namespace string            `yaml:"namespace,omitempty"`
@@ -178,19 +166,3 @@ func (d *PodDoc) ListImages() []string {
 	}
 	return images
 }
-
-func (d *SupportBundleDoc) ListImages() []string {
-	images := make([]string, 0)
-	for _, collector := range d.Spec.Collectors {
-		if collector.Run != nil {
-			images = append(images, collector.Run.Image)
-		}
-	}
-
-	return images
-}
-
-func (d *SupportBundleDoc) PatchWithPullSecret(secret *corev1.Secret) K8sDoc {
-	// this is not a kustomize patch, so we return nil and it's ignored
-	return nil
-}
diff --git a/pkg/kotsutil/kots.go b/pkg/kotsutil/kots.go
index e4f7e12cc..ef52688ad 100644
--- a/pkg/kotsutil/kots.go
+++ b/pkg/kotsutil/kots.go
@@ -393,20 +393,6 @@ func LoadInstallationFromPath(installationFilePath string) (*kotsv1beta1.Install
 	return LoadInstallationFromContents(installationData)
 }
 
-func LoadSupportBundleFromContents(data []byte) (*troubleshootv1beta2.SupportBundle, error) {
-	decode := scheme.Codecs.UniversalDeserializer().Decode
-	obj, gvk, err := decode([]byte(data), nil, nil)
-	if err != nil {
-		return nil, errors.Wrapf(err, "failed to decode support bundle data of length %d", len(data))
-	}
-
-	if gvk.Group != "troubleshoot.sh" || gvk.Version != "v1beta2" || gvk.Kind != "SupportBundle" {
-		return nil, errors.Errorf("unexpected GVK: %s", gvk.String())
-	}
-
-	return obj.(*troubleshootv1beta2.SupportBundle), nil
-}
-
 func LoadKotsAppFromContents(data []byte) (*kotsv1beta1.Application, error) {
 	decode := scheme.Codecs.UniversalDeserializer().Decode
 	obj, gvk, err := decode([]byte(data), nil, nil)
diff --git a/pkg/midstream/write.go b/pkg/midstream/write.go
index 792a38ec3..dedb65c6c 100644
--- a/pkg/midstream/write.go
+++ b/pkg/midstream/write.go
@@ -165,9 +165,6 @@ func (m *Midstream) writeObjectsWithPullSecret(options WriteOptions) error {
 
 	for _, o := range m.DocForPatches {
 		withPullSecret := o.PatchWithPullSecret(m.PullSecret)
-		if withPullSecret == nil {
-			continue
-		}
 
 		b, err := yaml.Marshal(withPullSecret)
 		if err != nil {
diff --git a/pkg/pull/peek.go b/pkg/pull/peek.go
index b43b925d8..db4686cae 100644
--- a/pkg/pull/peek.go
+++ b/pkg/pull/peek.go
@@ -5,20 +5,21 @@ import (
 	kotsv1beta1 "github.com/replicatedhq/kots/kotskinds/apis/kots/v1beta1"
 	"github.com/replicatedhq/kots/pkg/logger"
 	"github.com/replicatedhq/kots/pkg/upstream"
-	upstreamtypes "github.com/replicatedhq/kots/pkg/upstream/types"
 )
 
 type GetUpdatesOptions struct {
-	HelmRepoURI         string
-	Namespace           string
-	LocalPath           string
-	License             *kotsv1beta1.License
-	CurrentCursor       string
-	CurrentChannelID    string
-	CurrentChannelName  string
-	CurrentVersionLabel string
-	ReportingInfo       *upstreamtypes.ReportingInfo
-	Silent              bool
+	HelmRepoURI           string
+	Namespace             string
+	LocalPath             string
+	License               *kotsv1beta1.License
+	CurrentCursor         string
+	CurrentChannelID      string
+	CurrentChannelName    string
+	CurrentVersionLabel   string
+	DownstreamCursor      string
+	DownstreamChannelID   string
+	DownstreamChannelName string
+	Silent                bool
 }
 
 // GetUpdates will retrieve all later versions of the application specified in upstreamURI
@@ -32,14 +33,16 @@ func GetUpdates(upstreamURI string, getUpdatesOptions GetUpdatesOptions) ([]upst
 
 	log.Initialize()
 
-	fetchOptions := upstreamtypes.FetchOptions{}
+	fetchOptions := upstream.FetchOptions{}
 	fetchOptions.HelmRepoURI = getUpdatesOptions.HelmRepoURI
 	fetchOptions.LocalPath = getUpdatesOptions.LocalPath
 	fetchOptions.CurrentCursor = getUpdatesOptions.CurrentCursor
 	fetchOptions.CurrentChannelID = getUpdatesOptions.CurrentChannelID
 	fetchOptions.CurrentChannelName = getUpdatesOptions.CurrentChannelName
 	fetchOptions.CurrentVersionLabel = getUpdatesOptions.CurrentVersionLabel
-	fetchOptions.ReportingInfo = getUpdatesOptions.ReportingInfo
+	fetchOptions.DownstreamCursor = getUpdatesOptions.DownstreamCursor
+	fetchOptions.DownstreamChannelID = getUpdatesOptions.DownstreamChannelID
+	fetchOptions.DownstreamChannelName = getUpdatesOptions.DownstreamChannelName
 
 	if getUpdatesOptions.License != nil {
 		fetchOptions.License = getUpdatesOptions.License
diff --git a/pkg/pull/pull.go b/pkg/pull/pull.go
index 1a53c0170..f2ab96178 100644
--- a/pkg/pull/pull.go
+++ b/pkg/pull/pull.go
@@ -109,14 +109,14 @@ func Pull(upstreamURI string, pullOptions PullOptions) (string, error) {
 		return "", errors.Wrap(err, "failed to parse uri")
 	}
 
-	fetchOptions := upstreamtypes.FetchOptions{
+	fetchOptions := upstream.FetchOptions{
 		HelmRepoURI:   pullOptions.HelmRepoURI,
 		RootDir:       pullOptions.RootDir,
 		UseAppDir:     pullOptions.CreateAppDir,
 		LocalPath:     pullOptions.LocalPath,
 		CurrentCursor: pullOptions.UpdateCursor,
 		AppSequence:   pullOptions.AppSequence,
-		LocalRegistry: upstreamtypes.LocalRegistry{
+		LocalRegistry: upstream.LocalRegistry{
 			Host:      pullOptions.RewriteImageOptions.Host,
 			Namespace: pullOptions.RewriteImageOptions.Namespace,
 			Username:  pullOptions.RewriteImageOptions.Username,
diff --git a/pkg/rewrite/rewrite.go b/pkg/rewrite/rewrite.go
index 7ed0eebd2..7ffc8b88b 100644
--- a/pkg/rewrite/rewrite.go
+++ b/pkg/rewrite/rewrite.go
@@ -57,14 +57,14 @@ func Rewrite(rewriteOptions RewriteOptions) error {
 		rewriteOptions.ReportWriter = ioutil.Discard
 	}
 
-	fetchOptions := &upstreamtypes.FetchOptions{
+	fetchOptions := &upstream.FetchOptions{
 		RootDir:             rewriteOptions.RootDir,
 		LocalPath:           rewriteOptions.UpstreamPath,
 		CurrentCursor:       rewriteOptions.Installation.Spec.UpdateCursor,
 		CurrentVersionLabel: rewriteOptions.Installation.Spec.VersionLabel,
 		EncryptionKey:       rewriteOptions.Installation.Spec.EncryptionKey,
 		License:             rewriteOptions.License,
-		LocalRegistry: upstreamtypes.LocalRegistry{
+		LocalRegistry: upstream.LocalRegistry{
 			Host:      rewriteOptions.RegistryEndpoint,
 			Namespace: rewriteOptions.RegistryNamespace,
 			Username:  rewriteOptions.RegistryUsername,
diff --git a/pkg/upstream/fetch.go b/pkg/upstream/fetch.go
index cd9bd696a..43d3f4961 100644
--- a/pkg/upstream/fetch.go
+++ b/pkg/upstream/fetch.go
@@ -4,12 +4,42 @@ import (
 	"net/url"
 
 	"github.com/pkg/errors"
+	kotsv1beta1 "github.com/replicatedhq/kots/kotskinds/apis/kots/v1beta1"
 	"github.com/replicatedhq/kots/pkg/crypto"
 	"github.com/replicatedhq/kots/pkg/upstream/types"
 	"github.com/replicatedhq/kots/pkg/util"
 )
 
-func FetchUpstream(upstreamURI string, fetchOptions *types.FetchOptions) (*types.Upstream, error) {
+type FetchOptions struct {
+	RootDir               string
+	UseAppDir             bool
+	HelmRepoName          string
+	HelmRepoURI           string
+	HelmOptions           []string
+	LocalPath             string
+	License               *kotsv1beta1.License
+	ConfigValues          *kotsv1beta1.ConfigValues
+	Airgap                *kotsv1beta1.Airgap
+	EncryptionKey         string
+	CurrentCursor         string
+	CurrentChannelID      string
+	CurrentChannelName    string
+	CurrentVersionLabel   string
+	DownstreamCursor      string
+	DownstreamChannelID   string
+	DownstreamChannelName string
+	AppSequence           int64
+	LocalRegistry         LocalRegistry
+}
+
+type LocalRegistry struct {
+	Host      string
+	Namespace string
+	Username  string
+	Password  string
+}
+
+func FetchUpstream(upstreamURI string, fetchOptions *FetchOptions) (*types.Upstream, error) {
 	upstream, err := downloadUpstream(upstreamURI, fetchOptions)
 	if err != nil {
 		return nil, errors.Wrap(err, "download upstream failed")
@@ -18,7 +48,7 @@ func FetchUpstream(upstreamURI string, fetchOptions *types.FetchOptions) (*types
 	return upstream, nil
 }
 
-func downloadUpstream(upstreamURI string, fetchOptions *types.FetchOptions) (*types.Upstream, error) {
+func downloadUpstream(upstreamURI string, fetchOptions *FetchOptions) (*types.Upstream, error) {
 	if !util.IsURL(upstreamURI) {
 		return readFilesFromPath(upstreamURI)
 	}
@@ -52,14 +82,14 @@ func downloadUpstream(upstreamURI string, fetchOptions *types.FetchOptions) (*ty
 	return nil, errors.Errorf("unknown protocol scheme %q", u.Scheme)
 }
 
-func pickVersionLabel(fetchOptions *types.FetchOptions) string {
+func pickVersionLabel(fetchOptions *FetchOptions) string {
 	if fetchOptions.Airgap != nil && fetchOptions.Airgap.Spec.VersionLabel != "" {
 		return fetchOptions.Airgap.Spec.VersionLabel
 	}
 	return fetchOptions.CurrentVersionLabel
 }
 
-func pickCursor(fetchOptions *types.FetchOptions) ReplicatedCursor {
+func pickCursor(fetchOptions *FetchOptions) ReplicatedCursor {
 	if fetchOptions.Airgap != nil && fetchOptions.Airgap.Spec.UpdateCursor != "" {
 		return ReplicatedCursor{
 			ChannelID:   fetchOptions.Airgap.Spec.ChannelID,
diff --git a/pkg/upstream/peek.go b/pkg/upstream/peek.go
index a21258f87..6a44edc4a 100644
--- a/pkg/upstream/peek.go
+++ b/pkg/upstream/peek.go
@@ -4,7 +4,6 @@ import (
 	"net/url"
 
 	"github.com/pkg/errors"
-	types "github.com/replicatedhq/kots/pkg/upstream/types"
 	"github.com/replicatedhq/kots/pkg/util"
 )
 
@@ -13,7 +12,7 @@ type Update struct {
 	VersionLabel string `json:"versionLabel"`
 }
 
-func GetUpdatesUpstream(upstreamURI string, fetchOptions *types.FetchOptions) ([]Update, error) {
+func GetUpdatesUpstream(upstreamURI string, fetchOptions *FetchOptions) ([]Update, error) {
 	versions, err := getUpdatesUpstream(upstreamURI, fetchOptions)
 	if err != nil {
 		return nil, errors.Wrap(err, "download upstream failed")
@@ -22,7 +21,7 @@ func GetUpdatesUpstream(upstreamURI string, fetchOptions *types.FetchOptions) ([
 	return versions, nil
 }
 
-func getUpdatesUpstream(upstreamURI string, fetchOptions *types.FetchOptions) ([]Update, error) {
+func getUpdatesUpstream(upstreamURI string, fetchOptions *FetchOptions) ([]Update, error) {
 	if !util.IsURL(upstreamURI) {
 		return nil, errors.New("not implemented")
 	}
@@ -40,7 +39,12 @@ func getUpdatesUpstream(upstreamURI string, fetchOptions *types.FetchOptions) ([
 			ChannelName: fetchOptions.CurrentChannelName,
 			Cursor:      fetchOptions.CurrentCursor,
 		}
-		return getUpdatesReplicated(u, fetchOptions.LocalPath, currentCursor, fetchOptions.CurrentVersionLabel, fetchOptions.License, fetchOptions.ReportingInfo)
+		downstreamCursor := ReplicatedCursor{
+			ChannelID:   fetchOptions.DownstreamChannelID,
+			ChannelName: fetchOptions.DownstreamChannelName,
+			Cursor:      fetchOptions.DownstreamCursor,
+		}
+		return getUpdatesReplicated(u, fetchOptions.LocalPath, currentCursor, fetchOptions.CurrentVersionLabel, downstreamCursor, fetchOptions.License)
 	}
 	if u.Scheme == "git" {
 		// return getUpdatesGit(upstreamURI)
diff --git a/pkg/upstream/replicated.go b/pkg/upstream/replicated.go
index b351bfa84..790e1c2a7 100644
--- a/pkg/upstream/replicated.go
+++ b/pkg/upstream/replicated.go
@@ -78,7 +78,7 @@ func (this ReplicatedCursor) Equal(other ReplicatedCursor) bool {
 	return this.ChannelName == other.ChannelName && this.Cursor == other.Cursor
 }
 
-func getUpdatesReplicated(u *url.URL, localPath string, currentCursor ReplicatedCursor, currentVersionLabel string, license *kotsv1beta1.License, reportingInfo *types.ReportingInfo) ([]Update, error) {
+func getUpdatesReplicated(u *url.URL, localPath string, currentCursor ReplicatedCursor, currentVersionLabel string, downstreamCursor ReplicatedCursor, license *kotsv1beta1.License) ([]Update, error) {
 	if localPath != "" {
 		parsedLocalRelease, err := readReplicatedAppFromLocalPath(localPath, currentCursor, currentVersionLabel)
 		if err != nil {
@@ -103,7 +103,7 @@ func getUpdatesReplicated(u *url.URL, localPath string, currentCursor Replicated
 		return nil, errors.Wrap(err, "failed to get successful head response")
 	}
 
-	pendingReleases, err := listPendingChannelReleases(replicatedUpstream, remoteLicense, currentCursor, reportingInfo)
+	pendingReleases, err := listPendingChannelReleases(replicatedUpstream, remoteLicense, currentCursor, downstreamCursor)
 	if err != nil {
 		return nil, errors.Wrap(err, "failed to list replicated app releases")
 	}
@@ -118,7 +118,7 @@ func getUpdatesReplicated(u *url.URL, localPath string, currentCursor Replicated
 	return updates, nil
 }
 
-func downloadReplicated(u *url.URL, localPath string, rootDir string, useAppDir bool, license *kotsv1beta1.License, existingConfigValues *kotsv1beta1.ConfigValues, updateCursor ReplicatedCursor, versionLabel string, cipher *crypto.AESCipher, appSequence int64, isAirgap bool, registry types.LocalRegistry) (*types.Upstream, error) {
+func downloadReplicated(u *url.URL, localPath string, rootDir string, useAppDir bool, license *kotsv1beta1.License, existingConfigValues *kotsv1beta1.ConfigValues, updateCursor ReplicatedCursor, versionLabel string, cipher *crypto.AESCipher, appSequence int64, isAirgap bool, registry LocalRegistry) (*types.Upstream, error) {
 	var release *Release
 
 	if localPath != "" {
@@ -432,7 +432,7 @@ func downloadReplicatedApp(replicatedUpstream *ReplicatedUpstream, license *kots
 	return &release, nil
 }
 
-func listPendingChannelReleases(replicatedUpstream *ReplicatedUpstream, license *kotsv1beta1.License, currentCursor ReplicatedCursor, reportingInfo *types.ReportingInfo) ([]ChannelRelease, error) {
+func listPendingChannelReleases(replicatedUpstream *ReplicatedUpstream, license *kotsv1beta1.License, currentCursor ReplicatedCursor, downstreamCursor ReplicatedCursor) ([]ChannelRelease, error) {
 	u, err := url.Parse(license.Spec.Endpoint)
 	if err != nil {
 		return nil, errors.Wrap(err, "failed to parse endpoint from license")
@@ -453,23 +453,12 @@ func listPendingChannelReleases(replicatedUpstream *ReplicatedUpstream, license
 	urlValues := url.Values{}
 	urlValues.Set("channelSequence", sequence)
 	urlValues.Add("licenseSequence", fmt.Sprintf("%d", license.Spec.LicenseSequence))
+	urlValues.Add("downstreamChannelSequence", downstreamCursor.Cursor)
 
-	// reporting info
-	if reportingInfo != nil {
-		urlValues.Add("k8sVersion", reportingInfo.K8sVersion)
-		urlValues.Add("isKurl", fmt.Sprintf("%t", reportingInfo.IsKurl))
-		urlValues.Add("appStatus", reportingInfo.AppStatus)
-		urlValues.Add("clusterId", reportingInfo.ClusterID)
-		urlValues.Add("instanceId", reportingInfo.InstanceID)
-
-		if reportingInfo.DownstreamCursor != "" {
-			urlValues.Add("downstreamChannelSequence", reportingInfo.DownstreamCursor)
-		}
-		if reportingInfo.DownstreamChannelID != "" {
-			urlValues.Add("downstreamChannelId", reportingInfo.DownstreamChannelID)
-		} else if reportingInfo.DownstreamChannelName != "" {
-			urlValues.Add("downstreamChannelName", reportingInfo.DownstreamChannelName)
-		}
+	if downstreamCursor.ChannelID != "" {
+		urlValues.Add("downstreamChannelID", downstreamCursor.ChannelID)
+	} else {
+		urlValues.Add("downstreamChannelName", downstreamCursor.ChannelName)
 	}
 
 	url := fmt.Sprintf("%s://%s/release/%s/pending?%s", u.Scheme, hostname, license.Spec.AppSlug, urlValues.Encode())
diff --git a/pkg/upstream/types/types.go b/pkg/upstream/types/types.go
index 15c3d9eec..d6479dde1 100644
--- a/pkg/upstream/types/types.go
+++ b/pkg/upstream/types/types.go
@@ -3,7 +3,6 @@ package types
 import (
 	"path"
 
-	kotsv1beta1 "github.com/replicatedhq/kots/kotskinds/apis/kots/v1beta1"
 	kotsscheme "github.com/replicatedhq/kots/kotskinds/client/kotsclientset/scheme"
 	"k8s.io/client-go/kubernetes/scheme"
 )
@@ -46,44 +45,6 @@ type WriteOptions struct {
 	SharedPassword string
 }
 
-type FetchOptions struct {
-	RootDir             string
-	UseAppDir           bool
-	HelmRepoName        string
-	HelmRepoURI         string
-	HelmOptions         []string
-	LocalPath           string
-	License             *kotsv1beta1.License
-	ConfigValues        *kotsv1beta1.ConfigValues
-	Airgap              *kotsv1beta1.Airgap
-	EncryptionKey       string
-	CurrentCursor       string
-	CurrentChannelID    string
-	CurrentChannelName  string
-	CurrentVersionLabel string
-	AppSequence         int64
-	LocalRegistry       LocalRegistry
-	ReportingInfo       *ReportingInfo
-}
-
-type LocalRegistry struct {
-	Host      string
-	Namespace string
-	Username  string
-	Password  string
-}
-
-type ReportingInfo struct {
-	InstanceID            string
-	ClusterID             string
-	DownstreamCursor      string
-	DownstreamChannelID   string
-	DownstreamChannelName string
-	AppStatus             string
-	IsKurl                bool
-	K8sVersion            string
-}
-
 func (u *Upstream) GetUpstreamDir(options WriteOptions) string {
 	renderDir := options.RootDir
 	if options.CreateAppDir {


Test patch:
diff --git a/kotsadm/pkg/preflight/preflight_test.go b/kotsadm/pkg/preflight/preflight_test.go
index 1a7d0ee2e..60d0a146c 100644
--- a/kotsadm/pkg/preflight/preflight_test.go
+++ b/kotsadm/pkg/preflight/preflight_test.go
@@ -65,11 +65,10 @@ func Test_getPreflightState(t *testing.T) {
 			want:             "pass",
 		},
 	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			if got := getPreflightState(test.preflightResults); got != test.want {
-				t.Errorf("getPreflightState() = %v, want %v", got, test.want)
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := getPreflightState(tt.preflightResults); got != tt.want {
+				t.Errorf("getPreflightState() = %v, want %v", got, tt.want)
 			}
 		})
 	}
diff --git a/kotsadm/pkg/registry/troubleshoot_test.go b/kotsadm/pkg/registry/troubleshoot_test.go
deleted file mode 100644
index 4354abc4d..000000000
--- a/kotsadm/pkg/registry/troubleshoot_test.go
+++ /dev/null
@@ -1,189 +0,0 @@
-package registry
-
-import (
-	"testing"
-
-	registrytypes "github.com/replicatedhq/kots/kotsadm/pkg/registry/types"
-	kotsv1beta1 "github.com/replicatedhq/kots/kotskinds/apis/kots/v1beta1"
-	troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
-	"github.com/stretchr/testify/assert"
-	"github.com/stretchr/testify/require"
-)
-
-func Test_UpdateCollectorSpecsWithRegistryData(t *testing.T) {
-	tests := []struct {
-		name               string
-		knownImages        []kotsv1beta1.InstallationImage
-		localRegistryInfo  *registrytypes.RegistrySettings
-		license            *kotsv1beta1.License
-		collectors         []*troubleshootv1beta2.Collect
-		expectedCollectors []*troubleshootv1beta2.Collect
-	}{
-		{
-			name:               "empty spec, no change",
-			knownImages:        nil,
-			localRegistryInfo:  nil,
-			license:            nil,
-			collectors:         []*troubleshootv1beta2.Collect{},
-			expectedCollectors: []*troubleshootv1beta2.Collect{},
-		},
-		{
-			name:              "valid spec, no change",
-			knownImages:       nil,
-			localRegistryInfo: nil,
-			license:           nil,
-			collectors: []*troubleshootv1beta2.Collect{
-				{
-					ClusterInfo: &troubleshootv1beta2.ClusterInfo{},
-				},
-			},
-			expectedCollectors: []*troubleshootv1beta2.Collect{
-				{
-					ClusterInfo: &troubleshootv1beta2.ClusterInfo{},
-				},
-			},
-		},
-		{
-			name: "run collector, public image",
-			knownImages: []kotsv1beta1.InstallationImage{
-				{
-					Image:     "docker.io/bitnami/postgres:11",
-					IsPrivate: false,
-				},
-			},
-			localRegistryInfo: nil,
-			license:           nil,
-			collectors: []*troubleshootv1beta2.Collect{
-				{
-					Run: &troubleshootv1beta2.Run{
-						Image:           "docker.io/bitnami/postgres:11",
-						ImagePullSecret: nil,
-					},
-				},
-			},
-			expectedCollectors: []*troubleshootv1beta2.Collect{
-				{
-					Run: &troubleshootv1beta2.Run{
-						Image:           "docker.io/bitnami/postgres:11",
-						ImagePullSecret: nil,
-					},
-				},
-			},
-		},
-		{
-			name:        "run collector, public image, private local registry",
-			knownImages: []kotsv1beta1.InstallationImage{},
-			localRegistryInfo: &registrytypes.RegistrySettings{
-				Hostname:  "ttl.sh",
-				Namespace: "abc",
-				Username:  "user",
-				Password:  "pass",
-			},
-			license: nil,
-			collectors: []*troubleshootv1beta2.Collect{
-				{
-					Run: &troubleshootv1beta2.Run{
-						Image:           "docker.io/bitnami/postgres:11",
-						ImagePullSecret: nil,
-					},
-				},
-			},
-			expectedCollectors: []*troubleshootv1beta2.Collect{
-				{
-					Run: &troubleshootv1beta2.Run{
-						Image: "ttl.sh/abc/postgres:11",
-						ImagePullSecret: &troubleshootv1beta2.ImagePullSecrets{
-							SecretType: "kubernetes.io/dockerconfigjson",
-							Data: map[string]string{
-								".dockerconfigjson": "eyJhdXRocyI6eyJ0dGwuc2giOnsiYXV0aCI6ImRYTmxjanB3WVhOeiJ9fX0=",
-							},
-						},
-					},
-				},
-			},
-		},
-		{
-			name: "run collector, replicated registry image, no private local registry",
-			knownImages: []kotsv1beta1.InstallationImage{
-				{
-					Image:     "registry.replicated.com/my-app/my-image:abcdef",
-					IsPrivate: true,
-				},
-			},
-			localRegistryInfo: nil,
-			license: &kotsv1beta1.License{
-				Spec: kotsv1beta1.LicenseSpec{
-					LicenseID: "licenseid",
-					AppSlug:   "app-slug",
-				},
-			},
-			collectors: []*troubleshootv1beta2.Collect{
-				{
-					Run: &troubleshootv1beta2.Run{
-						Image: "registry.replicated.com/my-app/my-image:abcdef",
-					},
-				},
-			},
-			expectedCollectors: []*troubleshootv1beta2.Collect{
-				{
-					Run: &troubleshootv1beta2.Run{
-						Image: "registry.replicated.com/my-app/my-image:abcdef",
-						ImagePullSecret: &troubleshootv1beta2.ImagePullSecrets{
-							SecretType: "kubernetes.io/dockerconfigjson",
-							Data: map[string]string{
-								".dockerconfigjson": "eyJhdXRocyI6eyJyZWdpc3RyeS5yZXBsaWNhdGVkLmNvbSI6eyJhdXRoIjoiYkdsalpXNXpaV2xrT214cFkyVnVjMlZwWkE9PSJ9fX0=",
-							},
-						},
-					},
-				},
-			},
-		},
-		{
-			name: "run collector, private image (not replicated), no private local registry",
-			knownImages: []kotsv1beta1.InstallationImage{
-				{
-					Image:     "quay.io/my-app/my-image:abcdef",
-					IsPrivate: true,
-				},
-			},
-			localRegistryInfo: nil,
-			license: &kotsv1beta1.License{
-				Spec: kotsv1beta1.LicenseSpec{
-					LicenseID: "licenseid",
-					AppSlug:   "app-slug",
-				},
-			},
-			collectors: []*troubleshootv1beta2.Collect{
-				{
-					Run: &troubleshootv1beta2.Run{
-						Image: "quay.io/my-app/my-image:abcdef",
-					},
-				},
-			},
-			expectedCollectors: []*troubleshootv1beta2.Collect{
-				{
-					Run: &troubleshootv1beta2.Run{
-						Image: "proxy.replicated.com/proxy/app-slug/quay.io/my-app/my-image:abcdef",
-						ImagePullSecret: &troubleshootv1beta2.ImagePullSecrets{
-							SecretType: "kubernetes.io/dockerconfigjson",
-							Data: map[string]string{
-								".dockerconfigjson": "eyJhdXRocyI6eyJwcm94eS5yZXBsaWNhdGVkLmNvbSI6eyJhdXRoIjoiYkdsalpXNXpaV2xrT214cFkyVnVjMlZwWkE9PSJ9fX0=",
-							},
-						},
-					},
-				},
-			},
-		},
-	}
-
-	for _, test := range tests {
-		t.Run(test.name, func(t *testing.T) {
-			req := require.New(t)
-
-			actualCollectors, err := UpdateCollectorSpecsWithRegistryData(test.collectors, test.localRegistryInfo, test.knownImages, test.license)
-			req.NoError(err)
-
-			assert.Equal(t, test.expectedCollectors, actualCollectors)
-		})
-	}
-}
diff --git a/pkg/upstream/fetch_test.go b/pkg/upstream/fetch_test.go
index cfbe513e3..b1fd8ef01 100644
--- a/pkg/upstream/fetch_test.go
+++ b/pkg/upstream/fetch_test.go
@@ -8,7 +8,6 @@ import (
 	"testing"
 
 	kotsv1beta1 "github.com/replicatedhq/kots/kotskinds/apis/kots/v1beta1"
-	types "github.com/replicatedhq/kots/pkg/upstream/types"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -57,7 +56,7 @@ ACgAAA==`,
 	}
 
 	for _, test := range tests {
-		fetchOptions := &types.FetchOptions{
+		fetchOptions := &FetchOptions{
 			RootDir:             workDir,
 			LocalPath:           srcDir,
 			CurrentVersionLabel: test.currentVersionLabel,