Repository: argoproj/argo-cd
Pull request #11591 (2022-12-07 16:04:32)

chore(deps): bump express from 4.17.1 to 4.18.2 in /ui
Bumps [express](https://github.com/expressjs/express) from 4.17.1 to 4.18.2.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/expressjs/express/releases">express's releases</a>.</em></p>
<blockquote>
<h2>4.18.2</h2>
<ul>
<li>Fix regression routing a large stack in a single route</li>
<li>deps: body-parser@1.20.1
<ul>
<li>deps: qs@6.11.0</li>
<li>perf: remove unnecessary object clone</li>
</ul>
</li>
<li>deps: qs@6.11.0</li>
</ul>
<h2>4.18.1</h2>
<ul>
<li>Fix hanging on large stack of sync routes</li>
</ul>
<h2>4.18.0</h2>
<ul>
<li>Add &quot;root&quot; option to <code>res.download</code></li>
<li>Allow <code>options</code> without <code>filename</code> in <code>res.download</code></li>
<li>Deprecate string and non-integer arguments to <code>res.status</code></li>
<li>Fix behavior of <code>null</code>/<code>undefined</code> as <code>maxAge</code> in <code>res.cookie</code></li>
<li>Fix handling very large stacks of sync middleware</li>
<li>Ignore <code>Object.prototype</code> values in settings through <code>app.set</code>/<code>app.get</code></li>
<li>Invoke <code>default</code> with same arguments as types in <code>res.format</code></li>
<li>Support proper 205 responses using <code>res.send</code></li>
<li>Use <code>http-errors</code> for <code>res.format</code> error</li>
<li>deps: body-parser@1.20.0
<ul>
<li>Fix error message for json parse whitespace in <code>strict</code></li>
<li>Fix internal error when inflated body exceeds limit</li>
<li>Prevent loss of async hooks context</li>
<li>Prevent hanging when request already read</li>
<li>deps: depd@2.0.0</li>
<li>deps: http-errors@2.0.0</li>
<li>deps: on-finished@2.4.1</li>
<li>deps: qs@6.10.3</li>
<li>deps: raw-body@2.5.1</li>
</ul>
</li>
<li>deps: cookie@0.5.0
<ul>
<li>Add <code>priority</code> option</li>
<li>Fix <code>expires</code> option to reject invalid dates</li>
</ul>
</li>
<li>deps: depd@2.0.0
<ul>
<li>Replace internal <code>eval</code> usage with <code>Function</code> constructor</li>
<li>Use instance methods on <code>process</code> to check for listeners</li>
</ul>
</li>
<li>deps: finalhandler@1.2.0
<ul>
<li>Remove set content headers that break response</li>
<li>deps: on-finished@2.4.1</li>
<li>deps: statuses@2.0.1</li>
</ul>
</li>
<li>deps: on-finished@2.4.1
<ul>
<li>Prevent loss of async hooks context</li>
</ul>
</li>
<li>deps: qs@6.10.3</li>
<li>deps: send@0.18.0
<ul>
<li>Fix emitted 416 error missing headers property</li>
<li>Limit the headers removed for 304 response</li>
<li>deps: depd@2.0.0</li>
<li>deps: destroy@1.2.0</li>
<li>deps: http-errors@2.0.0</li>
<li>deps: on-finished@2.4.1</li>
</ul>
</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a href="https://github.com/expressjs/express/blob/master/History.md">express's changelog</a>.</em></p>
<blockquote>
<h1>4.18.2 / 2022-10-08</h1>
<ul>
<li>Fix regression routing a large stack in a single route</li>
<li>deps: body-parser@1.20.1
<ul>
<li>deps: qs@6.11.0</li>
<li>perf: remove unnecessary object clone</li>
</ul>
</li>
<li>deps: qs@6.11.0</li>
</ul>
<h1>4.18.1 / 2022-04-29</h1>
<ul>
<li>Fix hanging on large stack of sync routes</li>
</ul>
<h1>4.18.0 / 2022-04-25</h1>
<ul>
<li>Add &quot;root&quot; option to <code>res.download</code></li>
<li>Allow <code>options</code> without <code>filename</code> in <code>res.download</code></li>
<li>Deprecate string and non-integer arguments to <code>res.status</code></li>
<li>Fix behavior of <code>null</code>/<code>undefined</code> as <code>maxAge</code> in <code>res.cookie</code></li>
<li>Fix handling very large stacks of sync middleware</li>
<li>Ignore <code>Object.prototype</code> values in settings through <code>app.set</code>/<code>app.get</code></li>
<li>Invoke <code>default</code> with same arguments as types in <code>res.format</code></li>
<li>Support proper 205 responses using <code>res.send</code></li>
<li>Use <code>http-errors</code> for <code>res.format</code> error</li>
<li>deps: body-parser@1.20.0
<ul>
<li>Fix error message for json parse whitespace in <code>strict</code></li>
<li>Fix internal error when inflated body exceeds limit</li>
<li>Prevent loss of async hooks context</li>
<li>Prevent hanging when request already read</li>
<li>deps: depd@2.0.0</li>
<li>deps: http-errors@2.0.0</li>
<li>deps: on-finished@2.4.1</li>
<li>deps: qs@6.10.3</li>
<li>deps: raw-body@2.5.1</li>
</ul>
</li>
<li>deps: cookie@0.5.0
<ul>
<li>Add <code>priority</code> option</li>
<li>Fix <code>expires</code> option to reject invalid dates</li>
</ul>
</li>
<li>deps: depd@2.0.0
<ul>
<li>Replace internal <code>eval</code> usage with <code>Function</code> constructor</li>
<li>Use instance methods on <code>process</code> to check for listeners</li>
</ul>
</li>
<li>deps: finalhandler@1.2.0
<ul>
<li>Remove set content headers that break response</li>
<li>deps: on-finished@2.4.1</li>
<li>deps: statuses@2.0.1</li>
</ul>
</li>
<li>deps: on-finished@2.4.1
<ul>
<li>Prevent loss of async hooks context</li>
</ul>
</li>
<li>deps: qs@6.10.3</li>
<li>deps: send@0.18.0</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/expressjs/express/commit/8368dc178af16b91b576c4c1d135f701a0007e5d"><code>8368dc1</code></a> 4.18.2</li>
<li><a href="https://github.com/expressjs/express/commit/61f40491222dbede653b9938e6a4676f187aab44"><code>61f4049</code></a> docs: replace Freenode with Libera Chat</li>
<li><a href="https://github.com/expressjs/express/commit/bb7907b932afe3a19236a642f6054b6c8f7349a0"><code>bb7907b</code></a> build: Node.js@18.10</li>
<li><a href="https://github.com/expressjs/express/commit/f56ce73186e885a938bfdb3d3d1005a58e6ae12b"><code>f56ce73</code></a> build: supertest@6.3.0</li>
<li><a href="https://github.com/expressjs/express/commit/24b3dc551670ac4fb0cd5a2bd5ef643c9525e60f"><code>24b3dc5</code></a> deps: qs@6.11.0</li>
<li><a href="https://github.com/expressjs/express/commit/689d175b8b39d8860b81d723233fb83d15201827"><code>689d175</code></a> deps: body-parser@1.20.1</li>
<li><a href="https://github.com/expressjs/express/commit/340be0f79afb9b3176afb76235aa7f92acbd5050"><code>340be0f</code></a> build: eslint@8.24.0</li>
<li><a href="https://github.com/expressjs/express/commit/33e8dc303af9277f8a7e4f46abfdcb5e72f6797b"><code>33e8dc3</code></a> docs: use Node.js name style</li>
<li><a href="https://github.com/expressjs/express/commit/644f6464b9f61cbafa8f880636b1aa5237d95bad"><code>644f646</code></a> build: supertest@6.2.4</li>
<li><a href="https://github.com/expressjs/express/commit/ecd7572f1e920b7a512452b8d9806ae617a99c54"><code>ecd7572</code></a> build: Node.js@14.20</li>
<li>Additional commits viewable in <a href="https://github.com/expressjs/express/compare/4.17.1...4.18.2">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=express&package-manager=npm_and_yarn&previous-version=4.17.1&new-version=4.18.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/argoproj/argo-cd/network/alerts).

</details>


Patch:
diff --git a/USERS.md b/USERS.md
index 5a6e5cb5a..cc398ecfb 100644
--- a/USERS.md
+++ b/USERS.md
@@ -224,6 +224,7 @@ Currently, the following organizations are **officially** using Argo CD:
 1. [Toss](https://toss.im/en)
 1. [Trendyol](https://www.trendyol.com/)
 1. [tru.ID](https://tru.id)
+1. [Trusting Social](https://trustingsocial.com/)
 1. [Twilio SendGrid](https://sendgrid.com)
 1. [tZERO](https://www.tzero.com/)
 1. [UBIO](https://ub.io/)
diff --git a/VERSION b/VERSION
index 437459cd9..e70b4523a 100644
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-2.5.0
+2.6.0
diff --git a/applicationset/services/internal/github_app/client.go b/applicationset/services/internal/github_app/client.go
index 841396a2d..bad6e828a 100644
--- a/applicationset/services/internal/github_app/client.go
+++ b/applicationset/services/internal/github_app/client.go
@@ -20,10 +20,12 @@ func Client(g github_app_auth.Authentication, url string) (*github.Client, error
 		url = g.EnterpriseBaseURL
 	}
 	var client *github.Client
-	httpClient := http.Client{Transport: rt}
 	if url == "" {
+		httpClient := http.Client{Transport: rt}
 		client = github.NewClient(&httpClient)
 	} else {
+		rt.BaseURL = url
+		httpClient := http.Client{Transport: rt}
 		client, err = github.NewEnterpriseClient(url, url, &httpClient)
 		if err != nil {
 			return nil, fmt.Errorf("failed to create github enterprise client: %w", err)
diff --git a/assets/swagger.json b/assets/swagger.json
index 4d783849e..c48dbb3cd 100644
--- a/assets/swagger.json
+++ b/assets/swagger.json
@@ -735,6 +735,42 @@
         }
       }
     },
+    "/api/v1/applications/{name}/links": {
+      "get": {
+        "tags": [
+          "ApplicationService"
+        ],
+        "summary": "ListLinks returns the list of all application deep links",
+        "operationId": "ApplicationService_ListLinks",
+        "parameters": [
+          {
+            "type": "string",
+            "name": "name",
+            "in": "path",
+            "required": true
+          },
+          {
+            "type": "string",
+            "name": "namespace",
+            "in": "query"
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "A successful response.",
+            "schema": {
+              "$ref": "#/definitions/applicationLinksResponse"
+            }
+          },
+          "default": {
+            "description": "An unexpected error response.",
+            "schema": {
+              "$ref": "#/definitions/runtimeError"
+            }
+          }
+        }
+      }
+    },
     "/api/v1/applications/{name}/logs": {
       "get": {
         "tags": [
@@ -1385,6 +1421,67 @@
         }
       }
     },
+    "/api/v1/applications/{name}/resource/links": {
+      "get": {
+        "tags": [
+          "ApplicationService"
+        ],
+        "summary": "ListResourceLinks returns the list of all resource deep links",
+        "operationId": "ApplicationService_ListResourceLinks",
+        "parameters": [
+          {
+            "type": "string",
+            "name": "name",
+            "in": "path",
+            "required": true
+          },
+          {
+            "type": "string",
+            "name": "namespace",
+            "in": "query"
+          },
+          {
+            "type": "string",
+            "name": "resourceName",
+            "in": "query"
+          },
+          {
+            "type": "string",
+            "name": "version",
+            "in": "query"
+          },
+          {
+            "type": "string",
+            "name": "group",
+            "in": "query"
+          },
+          {
+            "type": "string",
+            "name": "kind",
+            "in": "query"
+          },
+          {
+            "type": "string",
+            "name": "appNamespace",
+            "in": "query"
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "A successful response.",
+            "schema": {
+              "$ref": "#/definitions/applicationLinksResponse"
+            }
+          },
+          "default": {
+            "description": "An unexpected error response.",
+            "schema": {
+              "$ref": "#/definitions/runtimeError"
+            }
+          }
+        }
+      }
+    },
     "/api/v1/applications/{name}/revisions/{revision}/metadata": {
       "get": {
         "tags": [
@@ -2560,6 +2657,37 @@
         }
       }
     },
+    "/api/v1/projects/{name}/links": {
+      "get": {
+        "tags": [
+          "ProjectService"
+        ],
+        "summary": "ListLinks returns all deep links for the particular project",
+        "operationId": "ProjectService_ListLinks",
+        "parameters": [
+          {
+            "type": "string",
+            "name": "name",
+            "in": "path",
+            "required": true
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "A successful response.",
+            "schema": {
+              "$ref": "#/definitions/applicationLinksResponse"
+            }
+          },
+          "default": {
+            "description": "An unexpected error response.",
+            "schema": {
+              "$ref": "#/definitions/runtimeError"
+            }
+          }
+        }
+      }
+    },
     "/api/v1/projects/{name}/syncwindows": {
       "get": {
         "tags": [
@@ -3900,6 +4028,34 @@
         }
       }
     },
+    "applicationLinkInfo": {
+      "type": "object",
+      "properties": {
+        "description": {
+          "type": "string"
+        },
+        "iconClass": {
+          "type": "string"
+        },
+        "title": {
+          "type": "string"
+        },
+        "url": {
+          "type": "string"
+        }
+      }
+    },
+    "applicationLinksResponse": {
+      "type": "object",
+      "properties": {
+        "items": {
+          "type": "array",
+          "items": {
+            "$ref": "#/definitions/applicationLinkInfo"
+          }
+        }
+      }
+    },
     "applicationLogEntry": {
       "type": "object",
       "properties": {
diff --git a/cmd/argocd-server/commands/argocd_server.go b/cmd/argocd-server/commands/argocd_server.go
index 55827a67d..df417e71f 100644
--- a/cmd/argocd-server/commands/argocd_server.go
+++ b/cmd/argocd-server/commands/argocd_server.go
@@ -71,6 +71,7 @@ func NewCommand() *cobra.Command {
 		dexServerStrictTLS       bool
 		staticAssetsDir          string
 		applicationNamespaces    []string
+		enableProxyExtension     bool
 	)
 	var command = &cobra.Command{
 		Use:               cliName,
@@ -184,6 +185,7 @@ func NewCommand() *cobra.Command {
 				RedisClient:           redisClient,
 				StaticAssetsDir:       staticAssetsDir,
 				ApplicationNamespaces: applicationNamespaces,
+				EnableProxyExtension:  enableProxyExtension,
 			}
 
 			stats.RegisterStackDumper()
@@ -235,6 +237,7 @@ func NewCommand() *cobra.Command {
 	command.Flags().BoolVar(&dexServerPlaintext, "dex-server-plaintext", env.ParseBoolFromEnv("ARGOCD_SERVER_DEX_SERVER_PLAINTEXT", false), "Use a plaintext client (non-TLS) to connect to dex server")
 	command.Flags().BoolVar(&dexServerStrictTLS, "dex-server-strict-tls", env.ParseBoolFromEnv("ARGOCD_SERVER_DEX_SERVER_STRICT_TLS", false), "Perform strict validation of TLS certificates when connecting to dex server")
 	command.Flags().StringSliceVar(&applicationNamespaces, "application-namespaces", env.StringsFromEnv("ARGOCD_APPLICATION_NAMESPACES", []string{}, ","), "List of additional namespaces where application resources can be managed in")
+	command.Flags().BoolVar(&enableProxyExtension, "enable-proxy-extension", env.ParseBoolFromEnv("ARGOCD_SERVER_ENABLE_PROXY_EXTENSION", false), "Enable Proxy Extension feature")
 	tlsConfigCustomizerSrc = tls.AddTLSFlagsToCmd(command)
 	cacheSrc = servercache.AddCacheFlagsToCmd(command, func(client *redis.Client) {
 		redisClient = client
diff --git a/cmd/argocd/commands/headless/headless.go b/cmd/argocd/commands/headless/headless.go
index 5b9a715c1..ba72cd5bd 100644
--- a/cmd/argocd/commands/headless/headless.go
+++ b/cmd/argocd/commands/headless/headless.go
@@ -202,17 +202,18 @@ func StartLocalServer(ctx context.Context, clientOpts *apiclient.ClientOptions,
 	}
 	appstateCache := appstatecache.NewCache(cache.NewCache(&forwardCacheClient{namespace: namespace, context: ctxStr}), time.Hour)
 	srv := server.NewServer(ctx, server.ArgoCDServerOpts{
-		EnableGZip:    false,
-		Namespace:     namespace,
-		ListenPort:    *port,
-		AppClientset:  appClientset,
-		DisableAuth:   true,
-		RedisClient:   redis.NewClient(&redis.Options{Addr: mr.Addr()}),
-		Cache:         servercache.NewCache(appstateCache, 0, 0, 0),
-		KubeClientset: kubeClientset,
-		Insecure:      true,
-		ListenHost:    *address,
-		RepoClientset: &forwardRepoClientset{namespace: namespace, context: ctxStr},
+		EnableGZip:           false,
+		Namespace:            namespace,
+		ListenPort:           *port,
+		AppClientset:         appClientset,
+		DisableAuth:          true,
+		RedisClient:          redis.NewClient(&redis.Options{Addr: mr.Addr()}),
+		Cache:                servercache.NewCache(appstateCache, 0, 0, 0),
+		KubeClientset:        kubeClientset,
+		Insecure:             true,
+		ListenHost:           *address,
+		RepoClientset:        &forwardRepoClientset{namespace: namespace, context: ctxStr},
+		EnableProxyExtension: false,
 	})
 	srv.Init(ctx)
 
diff --git a/controller/clusterinfoupdater.go b/controller/clusterinfoupdater.go
index 19459c66e..04ec12dec 100644
--- a/controller/clusterinfoupdater.go
+++ b/controller/clusterinfoupdater.go
@@ -117,7 +117,7 @@ func (c *clusterInfoUpdater) updateClusterInfo(cluster appv1.Cluster, info *cach
 	}
 	if info != nil {
 		clusterInfo.ServerVersion = info.K8SVersion
-		clusterInfo.APIVersions = argo.APIResourcesToStrings(info.APIResources, false)
+		clusterInfo.APIVersions = argo.APIResourcesToStrings(info.APIResources, true)
 		if info.LastCacheSyncTime == nil {
 			clusterInfo.ConnectionState.Status = appv1.ConnectionStatusUnknown
 		} else if info.SyncError == nil {
diff --git a/docs/operator-manual/argocd-cm.yaml b/docs/operator-manual/argocd-cm.yaml
index 5715f3a5d..851738380 100644
--- a/docs/operator-manual/argocd-cm.yaml
+++ b/docs/operator-manual/argocd-cm.yaml
@@ -305,3 +305,29 @@ data:
   # token verification to pass despite the OIDC provider having an invalid certificate. Only set to "true" if you
   # understand the risks.
   oidc.tls.insecure.skip.verify: "false"
+
+  # Add Deep Links to ArgoCD UI
+  # sample project level links
+  project.links: |
+    - url: https://myaudit-system.com?project={{.metadata.name}}
+      title: Audit
+      description: system audit logs
+      icon.class: "fa-book"
+  # sample application level links
+  application.links: |
+    # pkg.go.dev/text/template is used for evaluating url templates
+    - url: https://mycompany.splunk.com?search={{.spec.destination.namespace}}
+      title: Splunk
+    # conditionally show link e.g. for specific project
+    # github.com/antonmedv/expr is used for evaluation of conditions
+    - url: https://mycompany.splunk.com?search={{.spec.destination.namespace}}
+      title: Splunk
+      if: spec.project == "default"
+    - url: https://{{.metadata.annotations.splunkhost}}?search={{.spec.destination.namespace}}
+      title: Splunk
+      if: metadata.annotations.splunkhost
+  # sample resource level links
+  resource.links: |
+    - url: https://mycompany.splunk.com?search={{.metadata.namespace}}
+      title: Splunk
+      if: kind == "Pod" || kind == "Deployment"
\ No newline at end of file
diff --git a/docs/operator-manual/argocd-cmd-params-cm.yaml b/docs/operator-manual/argocd-cmd-params-cm.yaml
index cb490d3f4..bd2a119c0 100644
--- a/docs/operator-manual/argocd-cmd-params-cm.yaml
+++ b/docs/operator-manual/argocd-cmd-params-cm.yaml
@@ -104,6 +104,8 @@ data:
   server.app.state.cache.expiration: "1h0m0s"
   # Cache expiration default (default 24h0m0s)
   server.default.cache.expiration: "24h0m0s"
+  # Enable the experimental proxy extension feature
+  server.enable.proxy.extension: "false"
 
   ## Repo-server properties
   # Set the logging format. One of: text|json (default "text")
diff --git a/docs/operator-manual/server-commands/argocd-server.md b/docs/operator-manual/server-commands/argocd-server.md
index bf9b6d6c3..461ac194f 100644
--- a/docs/operator-manual/server-commands/argocd-server.md
+++ b/docs/operator-manual/server-commands/argocd-server.md
@@ -32,6 +32,7 @@ argocd-server [flags]
       --dex-server-strict-tls                         Perform strict validation of TLS certificates when connecting to dex server
       --disable-auth                                  Disable client authentication
       --enable-gzip                                   Enable GZIP compression
+      --enable-proxy-extension                        Enable Proxy Extension feature
       --gloglevel int                                 Set the glog logging level
   -h, --help                                          help for argocd-server
       --insecure                                      Run server without TLS
diff --git a/docs/operator-manual/web_based_terminal.md b/docs/operator-manual/web_based_terminal.md
index 25fccf2de..5c791e9fa 100644
--- a/docs/operator-manual/web_based_terminal.md
+++ b/docs/operator-manual/web_based_terminal.md
@@ -11,24 +11,25 @@ they have the `exec/create` privilege. If the Pod mounts a ServiceAccount token
 Kubernetes), then the user effectively has the same privileges as that ServiceAccount.
 
 ## Enabling the terminal
+<!-- Use indented code blocks for the numbered list to prevent breaking the numbering. See #11590 -->
 
 1. Set the `exec.enabled` key to `"true"` on the `argocd-cm` ConfigMap.
 
 2. Patch the `argocd-server` Role (if using namespaced Argo) or ClusterRole (if using clustered Argo) to allow `argocd-server`
 to exec into pods
-```yaml
-- apiGroups:
-  - ""
-  resources:
-  - pods/exec
-  verbs:
-  - create
-```
-
-3. Add RBAC rules to allow your users to `create` the `exec` resource, i.e.
-```
-p, role:myrole, exec, create, */*, allow
-```
+
+        - apiGroups:
+          - ""
+          resources:
+          - pods/exec
+          verbs:
+          - create
+
+
+3. Add RBAC rules to allow your users to `create` the `exec` resource, i.e. 
+
+        p, role:myrole, exec, create, */*, allow
+
 
 See [RBAC Configuration](rbac.md#exec-resource) for more info.
 
@@ -42,4 +43,4 @@ By default, Argo CD attempts to execute shells in this order:
 4. cmd
 
 If none of the shells are found, the terminal session will fail. To add to or change the allowed shells, change the 
-`exec.shells` key in the `argocd-cm` ConfigMap, separating them with commas.
\ No newline at end of file
+`exec.shells` key in the `argocd-cm` ConfigMap, separating them with commas.
diff --git a/docs/user-guide/projects.md b/docs/user-guide/projects.md
index 8b1d8850f..8ef1426dd 100644
--- a/docs/user-guide/projects.md
+++ b/docs/user-guide/projects.md
@@ -47,6 +47,33 @@ argocd proj add-source <PROJECT> <REPO>
 argocd proj remove-source <PROJECT> <REPO>
 ```
 
+We can also do negations of sources (i.e. do _not_ use this repo).
+
+```bash
+argocd proj add-source <PROJECT> !<REPO>
+argocd proj remove-source <PROJECT> !<REPO>
+```
+
+Declaratively we can do something like this:
+
+```yaml
+spec:
+  sourceRepos:
+    # Do not use the test repo in argoproj
+    - '!ssh://git@GITHUB.com:argoproj/test'
+    # Nor any Gitlab repo under group/ 
+    - '!https://gitlab.com/group/**'
+    # Any other repo is fine though
+    - '*'
+```
+
+A source repository is considered valid if the following conditions hold:
+
+1) _Any_ allow source rule (i.e. a rule which isn't prefixed with `!`) permits the source
+2) AND *no* deny source (i.e. a rule which is prefixed with `!`) rejects the source
+
+Keep in mind that `!*` is an invalid rule, since it doesn't make any sense to disallow everything.
+
 Permitted destination clusters and namespaces are managed with the commands (for clusters always provide server, the name is not used for matching):
 
 ```bash
@@ -54,7 +81,7 @@ argocd proj add-destination <PROJECT> <CLUSTER>,<NAMESPACE>
 argocd proj remove-destination <PROJECT> <CLUSTER>,<NAMESPACE>
 ```
 
-We can also do negations of destinations (i.e. install anywhere _apart from_).
+As with sources, we can also do negations of destinations (i.e. install anywhere _apart from_).
 
 ```bash
 argocd proj add-destination <PROJECT> !<CLUSTER>,!<NAMESPACE>
@@ -77,7 +104,7 @@ spec:
     server: '*'
 ```
 
-A destination is considered valid if the following conditions hold:
+As with sources, a destination is considered valid if the following conditions hold:
 
 1) _Any_ allow destination rule (i.e. a rule which isn't prefixed with `!`) permits the destination
 2) AND *no* deny destination (i.e. a rule which is prefixed with `!`) rejects the destination
diff --git a/docs/user-guide/sync-options.md b/docs/user-guide/sync-options.md
index 32501b273..7395cc46b 100644
--- a/docs/user-guide/sync-options.md
+++ b/docs/user-guide/sync-options.md
@@ -38,8 +38,8 @@ metadata:
     argocd.argoproj.io/sync-options: Validate=false
 ```
 
-If you want to exclude a whole class of objects globally, consider setting `resource.customizations` in [system level configuration](../user-guide/diffing.md#system-level-configuration). 
-    
+If you want to exclude a whole class of objects globally, consider setting `resource.customizations` in [system level configuration](../user-guide/diffing.md#system-level-configuration).
+
 ## Skip Dry Run for new custom resources types
 
 When syncing a custom resource which is not yet known to the cluster, there are generally two options:
@@ -60,9 +60,9 @@ The dry run will still be executed if the CRD is already present in the cluster.
 
 ## Selective Sync
 
-Currently when syncing using auto sync Argo CD applies every object in the application. 
+Currently when syncing using auto sync Argo CD applies every object in the application.
 For applications containing thousands of objects this takes quite a long time and puts undue pressure on the api server.
-Turning on selective sync option which will sync only out-of-sync resources. 
+Turning on selective sync option which will sync only out-of-sync resources.
 
 You can add this option by following ways
 
@@ -77,7 +77,7 @@ spec:
   syncPolicy:
     syncOptions:
     - ApplyOutOfSyncOnly=true
-``` 
+```
 
 2) Set sync option via argocd cli
 
@@ -104,8 +104,8 @@ spec:
 
 ## Prune Last
 
-This feature is to allow the ability for resource pruning to happen as a final, implicit wave of a sync operation, 
-after the other resources have been deployed and become healthy, and after all other waves completed successfully. 
+This feature is to allow the ability for resource pruning to happen as a final, implicit wave of a sync operation,
+after the other resources have been deployed and become healthy, and after all other waves completed successfully.
 
 ```yaml
 apiVersion: argoproj.io/v1alpha1
@@ -142,6 +142,10 @@ spec:
 
 If the `Replace=true` sync option is set the Argo CD will use `kubectl replace` or `kubectl create` command to apply changes.
 
+!!! warning
+      During the sync process, the resources will be synchronized using the 'kubectl replace/create' command.
+      This sync option has the potential to be destructive and might lead to resources having to be recreated, which could cause an outage for your application.
+
 This can also be configured at individual resource level.
 ```yaml
 metadata:
@@ -299,8 +303,8 @@ sync option, otherwise nothing will happen. If the namespace doesn't already exi
 already have labels and/or annotations set on it, you're good to go. Using `managedNamespaceMetadata` will also set the
 resource tracking label (or annotation) on the namespace, so you can easily track which namespaces are managed by ArgoCD.
 
-In the case you do not have any custom annotations or labels but would nonetheless want to have resource tracking set on 
-your namespace, that can be done by setting `managedNamespaceMetadata` with an empty `labels` and/or `annotations` map, 
+In the case you do not have any custom annotations or labels but would nonetheless want to have resource tracking set on
+your namespace, that can be done by setting `managedNamespaceMetadata` with an empty `labels` and/or `annotations` map,
 like the example below:
 
 ```yaml
@@ -311,7 +315,7 @@ metadata:
 spec:
   syncPolicy:
     managedNamespaceMetadata:
-      labels: # The labels to set on the application namespace 
+      labels: # The labels to set on the application namespace
       annotations: # The annotations to set on the application namespace
     syncOptions:
     - CreateNamespace=true
@@ -374,7 +378,7 @@ spec:
   syncPolicy:
     managedNamespaceMetadata:
       annotations:
-        abc: 123 
+        abc: 123
     syncOptions:
       - CreateNamespace=true
 ```
@@ -398,4 +402,3 @@ The resulting namespace will have its annotations set to
     foo: bar
     something: completely-different
 ```
-
diff --git a/go.mod b/go.mod
index f19049a09..cb834ba42 100644
--- a/go.mod
+++ b/go.mod
@@ -5,16 +5,16 @@ go 1.18
 require (
 	code.gitea.io/sdk/gitea v0.15.1
 	github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible
-	github.com/Masterminds/semver/v3 v3.1.1
+	github.com/Masterminds/semver/v3 v3.2.0
 	github.com/TomOnTime/utfutil v0.0.0-20180511104225-09c41003ee1d
 	github.com/alicebob/miniredis/v2 v2.23.1
 	github.com/argoproj/gitops-engine v0.7.1-0.20221108210551-e284fd71cb96
 	github.com/argoproj/notifications-engine v0.3.1-0.20221203221941-490d98afd1d6
 	github.com/argoproj/pkg v0.13.7-0.20221115212233-27bd8ce31415
-	github.com/aws/aws-sdk-go v1.44.129
+	github.com/aws/aws-sdk-go v1.44.156
 	github.com/bombsimon/logrusr/v2 v2.0.1
 	github.com/bradleyfalzon/ghinstallation/v2 v2.1.0
-	github.com/casbin/casbin/v2 v2.57.1
+	github.com/casbin/casbin/v2 v2.59.0
 	github.com/chai2010/gettext-go v0.0.0-20170215093142-bf70f2a70fb1 // indirect
 	github.com/coreos/go-oidc v2.2.1+incompatible
 	github.com/dustin/go-humanize v1.0.0
@@ -23,12 +23,12 @@ require (
 	github.com/ghodss/yaml v1.0.0
 	github.com/go-git/go-git/v5 v5.4.2
 	github.com/go-logr/logr v1.2.3
-	github.com/go-openapi/loads v0.21.1
+	github.com/go-openapi/loads v0.21.2
 	github.com/go-openapi/runtime v0.25.0
-	github.com/go-openapi/spec v0.20.4 // indirect
+	github.com/go-openapi/spec v0.20.6 // indirect
 	github.com/go-openapi/validate v0.21.0 // indirect
 	github.com/go-redis/cache/v8 v8.4.2
-	github.com/go-redis/redis/v8 v8.11.3
+	github.com/go-redis/redis/v8 v8.11.5
 	github.com/gobwas/glob v0.2.3
 	github.com/gogits/go-gogs-client v0.0.0-20190616193657-5a05380e4bc2
 	github.com/gogo/protobuf v1.3.2
@@ -40,6 +40,7 @@ require (
 	github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
 	github.com/google/uuid v1.3.0
 	github.com/gorilla/handlers v1.5.1
+	github.com/gorilla/mux v1.8.0
 	github.com/gorilla/websocket v1.4.2
 	github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
 	github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
@@ -73,10 +74,10 @@ require (
 	github.com/xanzy/go-gitlab v0.60.0
 	github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64
 	golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa
-	golang.org/x/net v0.0.0-20220906165146-f3363e06e74c // indirect
+	golang.org/x/net v0.1.0 // indirect
 	golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb
 	golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4
-	golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
+	golang.org/x/term v0.1.0
 	google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368
 	google.golang.org/grpc v1.51.0
 	google.golang.org/protobuf v1.28.1
@@ -106,6 +107,7 @@ require (
 
 require (
 	github.com/Masterminds/sprig/v3 v3.2.2
+	github.com/antonmedv/expr v1.9.0
 	github.com/gosimple/slug v1.13.1
 	github.com/microsoft/azure-devops-go-api/azuredevops v1.0.0-b5
 	github.com/robfig/cron/v3 v3.0.1
@@ -139,12 +141,9 @@ require (
 	github.com/Microsoft/go-winio v0.4.17 // indirect
 	github.com/PagerDuty/go-pagerduty v1.6.0 // indirect
 	github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 // indirect
-	github.com/PuerkitoBio/purell v1.1.1 // indirect
-	github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
 	github.com/RocketChat/Rocket.Chat.Go.SDK v0.0.0-20210112200207-10ab4d695d60 // indirect
 	github.com/acomagu/bufpipe v1.0.3 // indirect
 	github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a // indirect
-	github.com/antonmedv/expr v1.9.0 // indirect
 	github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect
 	github.com/beorn7/perks v1.0.1 // indirect
 	github.com/cenkalti/backoff/v4 v4.1.3 // indirect
@@ -163,13 +162,12 @@ require (
 	github.com/go-git/gcfg v1.5.0 // indirect
 	github.com/go-git/go-billy/v5 v5.3.1 // indirect
 	github.com/go-logr/stdr v1.2.2 // indirect
-	github.com/go-openapi/analysis v0.21.2 // indirect
+	github.com/go-openapi/analysis v0.21.4 // indirect
 	github.com/go-openapi/errors v0.20.2 // indirect
 	github.com/go-openapi/jsonpointer v0.19.5 // indirect
-	github.com/go-openapi/jsonreference v0.19.6 // indirect
-	github.com/go-openapi/strfmt v0.21.2 // indirect
+	github.com/go-openapi/jsonreference v0.20.0 // indirect
+	github.com/go-openapi/strfmt v0.21.3 // indirect
 	github.com/go-openapi/swag v0.21.1 // indirect
-	github.com/go-stack/stack v1.8.1 // indirect
 	github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 // indirect
 	github.com/golang/glog v1.0.0 // indirect
 	github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
@@ -226,7 +224,7 @@ require (
 	github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
 	github.com/xanzy/ssh-agent v0.3.0 // indirect
 	github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca // indirect
-	go.mongodb.org/mongo-driver v1.8.3 // indirect
+	go.mongodb.org/mongo-driver v1.10.0 // indirect
 	go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.11.1 // indirect
 	go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.11.1 // indirect
 	go.opentelemetry.io/otel/trace v1.11.1 // indirect
@@ -263,7 +261,6 @@ replace (
 	github.com/go-check/check => github.com/go-check/check v0.0.0-20180628173108-788fd7840127
 
 	github.com/golang/protobuf => github.com/golang/protobuf v1.4.2
-	github.com/gorilla/websocket => github.com/gorilla/websocket v1.4.2
 	github.com/grpc-ecosystem/grpc-gateway => github.com/grpc-ecosystem/grpc-gateway v1.16.0
 	github.com/improbable-eng/grpc-web => github.com/improbable-eng/grpc-web v0.0.0-20181111100011-16092bd1d58a
 
diff --git a/go.sum b/go.sum
index ec7244c60..06675e0aa 100644
--- a/go.sum
+++ b/go.sum
@@ -86,8 +86,9 @@ github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJ
 github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
 github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
 github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
-github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
 github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
+github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g=
+github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
 github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60=
 github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
 github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8=
@@ -105,9 +106,7 @@ github.com/PagerDuty/go-pagerduty v1.6.0 h1:am81SzvG5Pw+s3JZ5yEy6kGvsXXklTNRrGr3
 github.com/PagerDuty/go-pagerduty v1.6.0/go.mod h1:7eaBLzsDpK7VUvU0SJ5mohczQkoWrrr5CjDaw5gh1as=
 github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 h1:YoJbenK9C67SkzkDfmQuVln04ygHj3vjZfd9FL+GmQQ=
 github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
-github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
 github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
-github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
 github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
 github.com/RocketChat/Rocket.Chat.Go.SDK v0.0.0-20210112200207-10ab4d695d60 h1:prBTRx78AQnXzivNT9Crhu564W/zPPr3ibSlpT9xKcE=
 github.com/RocketChat/Rocket.Chat.Go.SDK v0.0.0-20210112200207-10ab4d695d60/go.mod h1:rjP7sIipbZcagro/6TCk6X0ZeFT2eyudH5+fve/cbBA=
@@ -160,8 +159,9 @@ github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQ
 github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
 github.com/aws/aws-sdk-go v1.35.24/go.mod h1:tlPOdRjfxPBpNIwqDj61rmsnA85v9jc0Ps9+muhnW+k=
 github.com/aws/aws-sdk-go v1.38.49/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
-github.com/aws/aws-sdk-go v1.44.129 h1:yld8Rc8OCahLtenY1mnve4w1jVeBu/rSiscGzodaDOs=
 github.com/aws/aws-sdk-go v1.44.129/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
+github.com/aws/aws-sdk-go v1.44.156 h1:3RhbBTZ87HoI5OP2JjcKdd5qOnyo9YOAW8+Bb/h0vSE=
+github.com/aws/aws-sdk-go v1.44.156/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
 github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
 github.com/beevik/ntp v0.2.0/go.mod h1:hIHWr+l3+/clUnF44zdK+CWW7fO8dR5cIylAQ76NRpg=
 github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM=
@@ -182,8 +182,8 @@ github.com/bradleyfalzon/ghinstallation/v2 v2.1.0 h1:5+NghM1Zred9Z078QEZtm28G/kf
 github.com/bradleyfalzon/ghinstallation/v2 v2.1.0/go.mod h1:Xg3xPRN5Mcq6GDqeUVhFbjEWMb4JHCyWEeeBGEYQoTU=
 github.com/bwmarrin/discordgo v0.19.0/go.mod h1:O9S4p+ofTFwB02em7jkpkV8M3R0/PUVOwN61zSZ0r4Q=
 github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
-github.com/casbin/casbin/v2 v2.57.1 h1:btDNjK0SPYIT2SpafN/HDDRwVEqQrrfyvoWK9qRMqpc=
-github.com/casbin/casbin/v2 v2.57.1/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=
+github.com/casbin/casbin/v2 v2.59.0 h1:kC1+P9/ZoJqGSls/68Ax31XJeDuTrWr3yPmsUFvGy34=
+github.com/casbin/casbin/v2 v2.59.0/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=
 github.com/cenkalti/backoff v2.1.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
 github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
 github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4=
@@ -382,8 +382,9 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
 github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
 github.com/go-logr/zapr v1.2.0 h1:n4JnPI1T3Qq1SFEi/F8rwLrZERp2bso19PJZDB9dayk=
 github.com/go-logr/zapr v1.2.0/go.mod h1:Qa4Bsj2Vb+FAVeAKsLD8RLQ+YRJB8YDmOAKxaBQf7Ro=
-github.com/go-openapi/analysis v0.21.2 h1:hXFrOYFHUAMQdu6zwAiKKJHJQ8kqZs1ux/ru1P1wLJU=
 github.com/go-openapi/analysis v0.21.2/go.mod h1:HZwRk4RRisyG8vx2Oe6aqeSQcoxRp47Xkp3+K6q+LdY=
+github.com/go-openapi/analysis v0.21.4 h1:ZDFLvSNxpDaomuCueM0BlSXxpANBlFYiBvr+GXrvIHc=
+github.com/go-openapi/analysis v0.21.4/go.mod h1:4zQ35W4neeZTqh3ol0rv/O8JBbka9QyAgQRPp9y3pfo=
 github.com/go-openapi/errors v0.19.8/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
 github.com/go-openapi/errors v0.19.9/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
 github.com/go-openapi/errors v0.20.2 h1:dxy7PGTqEh94zj2E3h1cUmQQWiM1+aeCROfAr02EmK8=
@@ -393,18 +394,21 @@ github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUe
 github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
 github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
 github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg=
-github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs=
 github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns=
-github.com/go-openapi/loads v0.21.1 h1:Wb3nVZpdEzDTcly8S4HMkey6fjARRzb7iEaySimlDW0=
+github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA=
+github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo=
 github.com/go-openapi/loads v0.21.1/go.mod h1:/DtAMXXneXFjbQMGEtbamCZb+4x7eGwkvZCvBmwUG+g=
+github.com/go-openapi/loads v0.21.2 h1:r2a/xFIYeZ4Qd2TnGpWDIQNcP80dIaZgf704za8enro=
+github.com/go-openapi/loads v0.21.2/go.mod h1:Jq58Os6SSGz0rzh62ptiu8Z31I+OTHqmULx5e/gJbNw=
 github.com/go-openapi/runtime v0.25.0 h1:7yQTCdRbWhX8vnIjdzU8S00tBYf7Sg71EBeorlPHvhc=
 github.com/go-openapi/runtime v0.25.0/go.mod h1:Ux6fikcHXyyob6LNWxtE96hWwjBPYF0DXgVFuMTneOs=
-github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M=
 github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I=
+github.com/go-openapi/spec v0.20.6 h1:ich1RQ3WDbfoeTqTAb+5EIxNmpKVJZWBNah9RAT0jIQ=
+github.com/go-openapi/spec v0.20.6/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA=
 github.com/go-openapi/strfmt v0.21.0/go.mod h1:ZRQ409bWMj+SOgXofQAGTIo2Ebu72Gs+WaRADcS5iNg=
 github.com/go-openapi/strfmt v0.21.1/go.mod h1:I/XVKeLc5+MM5oPNN7P6urMOpuLXEcNrCX/rPGuWb0k=
-github.com/go-openapi/strfmt v0.21.2 h1:5NDNgadiX1Vhemth/TH4gCGopWSTdDjxl60H3B7f+os=
-github.com/go-openapi/strfmt v0.21.2/go.mod h1:I/XVKeLc5+MM5oPNN7P6urMOpuLXEcNrCX/rPGuWb0k=
+github.com/go-openapi/strfmt v0.21.3 h1:xwhj5X6CjXEZZHMWy1zKJxvW9AfHC9pkyUjLvHtKG7o=
+github.com/go-openapi/strfmt v0.21.3/go.mod h1:k+RzNO0Da+k3FrrynSNN8F7n/peCmQQqbbXjtDfvmGg=
 github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
 github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
 github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
@@ -417,12 +421,11 @@ github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3yg
 github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY=
 github.com/go-redis/cache/v8 v8.4.2 h1:8YbsmnU1Ws3TKS6T+qALzYE/MlGE+A/lrlx1XTA3p6M=
 github.com/go-redis/cache/v8 v8.4.2/go.mod h1:X7Jjd69Ssbrf3xBQLtIDE0g3WcSbFoQiSGeb8QfEJ+g=
-github.com/go-redis/redis/v8 v8.11.3 h1:GCjoYp8c+yQTJfc0n69iwSiHjvuAdruxl7elnZCxgt8=
 github.com/go-redis/redis/v8 v8.11.3/go.mod h1:xNJ9xDG09FsIPwh3bWdk+0oDWHbtF9rPN0F/oD9XeKc=
+github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
+github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
 github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
-github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw=
-github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4=
 github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
 github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc=
 github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8=
@@ -578,7 +581,10 @@ github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv
 github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
 github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
 github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
+github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
 github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
+github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
+github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
 github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
 github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
 github.com/gosimple/slug v1.13.1 h1:bQ+kpX9Qa6tHRaK+fZR0A0M2Kd7Pa5eHPPsb1JpHD+Q=
@@ -1102,7 +1108,9 @@ github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI
 github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0=
 github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
 github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs=
+github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g=
 github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM=
+github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8=
 github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
 github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca h1:1CFlNzQhALwjS9mBAUkycX616GzgsuYUOCHA5+HSlXI=
 github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg=
@@ -1114,6 +1122,7 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
 github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
 github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64 h1:5mLPGnFdSsevFRFc9q3yYbBkB6tsm4aCwwQV/j1JQAQ=
 github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
 go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
@@ -1132,8 +1141,8 @@ go.etcd.io/etcd/raft/v3 v3.5.0/go.mod h1:UFOHSIvO/nKwd4lhkwabrTD3cqW5yVyYYf/KlD0
 go.etcd.io/etcd/server/v3 v3.5.0/go.mod h1:3Ah5ruV+M+7RZr0+Y/5mNLwC+eQlni+mQmOVdCRJoS4=
 go.mongodb.org/mongo-driver v1.7.3/go.mod h1:NqaYOwnXWr5Pm7AOpO5QFxKJ503nbMse/R79oO62zWg=
 go.mongodb.org/mongo-driver v1.7.5/go.mod h1:VXEWRZ6URJIkUq2SCAyapmhH0ZLRBP+FT4xhp5Zvxng=
-go.mongodb.org/mongo-driver v1.8.3 h1:TDKlTkGDKm9kkJVUOAXDK5/fkqKHJVwYQSpoRfB43R4=
-go.mongodb.org/mongo-driver v1.8.3/go.mod h1:0sQWfOeY63QTntERDJJ/0SuKK0T1uVSgKCuAROlKEPY=
+go.mongodb.org/mongo-driver v1.10.0 h1:UtV6N5k14upNp4LTduX0QCufG124fSu25Wz9tu94GLg=
+go.mongodb.org/mongo-driver v1.10.0/go.mod h1:wsihk0Kdgv8Kqu1Anit4sfK+22vSFbUrAVEYRhCXrA8=
 go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
 go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
 go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
@@ -1212,12 +1221,12 @@ golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPh
 golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
 golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
 golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
 golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c=
 golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -1331,8 +1340,8 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx
 golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
 golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
-golang.org/x/net v0.0.0-20220906165146-f3363e06e74c h1:yKufUcDwucU5urd+50/Opbt4AYpqthk7wHpHok8f1lo=
-golang.org/x/net v0.0.0-20220906165146-f3363e06e74c/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
+golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0=
+golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
 golang.org/x/oauth2 v0.0.0-20180227000427-d7d64896b5ff/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -1479,10 +1488,10 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
 golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw=
+golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
diff --git a/hack/generate-proto.sh b/hack/generate-proto.sh
index cdd3cf283..d544bd045 100755
--- a/hack/generate-proto.sh
+++ b/hack/generate-proto.sh
@@ -125,7 +125,7 @@ clean_swagger() {
 }
 
 echo "If additional types are added, the number of expected collisions may need to be increased"
-EXPECTED_COLLISION_COUNT=93
+EXPECTED_COLLISION_COUNT=95
 collect_swagger server ${EXPECTED_COLLISION_COUNT}
 clean_swagger server
 clean_swagger reposerver
diff --git a/manifests/base/server/argocd-server-deployment.yaml b/manifests/base/server/argocd-server-deployment.yaml
index 3f5c6c5c2..b5a9e405b 100644
--- a/manifests/base/server/argocd-server-deployment.yaml
+++ b/manifests/base/server/argocd-server-deployment.yaml
@@ -208,6 +208,12 @@ spec:
                 name: argocd-cmd-params-cm
                 key: application.namespaces
                 optional: true
+        - name: ARGOCD_SERVER_ENABLE_PROXY_EXTENSION
+          valueFrom:
+              configMapKeyRef:
+                name: argocd-cmd-params-cm
+                key: server.enable.proxy.extension
+                optional: true
         volumeMounts:
         - name: ssh-known-hosts
           mountPath: /app/config/ssh
diff --git a/manifests/ha/install.yaml b/manifests/ha/install.yaml
index f99f2ea32..69a79fff1 100644
--- a/manifests/ha/install.yaml
+++ b/manifests/ha/install.yaml
@@ -12465,6 +12465,12 @@ spec:
               key: application.namespaces
               name: argocd-cmd-params-cm
               optional: true
+        - name: ARGOCD_SERVER_ENABLE_PROXY_EXTENSION
+          valueFrom:
+            configMapKeyRef:
+              key: server.enable.proxy.extension
+              name: argocd-cmd-params-cm
+              optional: true
         image: quay.io/argoproj/argocd:latest
         imagePullPolicy: Always
         livenessProbe:
diff --git a/manifests/ha/namespace-install.yaml b/manifests/ha/namespace-install.yaml
index 43b7c31b1..d3613c05d 100644
--- a/manifests/ha/namespace-install.yaml
+++ b/manifests/ha/namespace-install.yaml
@@ -2388,6 +2388,12 @@ spec:
               key: application.namespaces
               name: argocd-cmd-params-cm
               optional: true
+        - name: ARGOCD_SERVER_ENABLE_PROXY_EXTENSION
+          valueFrom:
+            configMapKeyRef:
+              key: server.enable.proxy.extension
+              name: argocd-cmd-params-cm
+              optional: true
         image: quay.io/argoproj/argocd:latest
         imagePullPolicy: Always
         livenessProbe:
diff --git a/manifests/install.yaml b/manifests/install.yaml
index e373fc4ba..daad019c6 100644
--- a/manifests/install.yaml
+++ b/manifests/install.yaml
@@ -11495,6 +11495,12 @@ spec:
               key: application.namespaces
               name: argocd-cmd-params-cm
               optional: true
+        - name: ARGOCD_SERVER_ENABLE_PROXY_EXTENSION
+          valueFrom:
+            configMapKeyRef:
+              key: server.enable.proxy.extension
+              name: argocd-cmd-params-cm
+              optional: true
         image: quay.io/argoproj/argocd:latest
         imagePullPolicy: Always
         livenessProbe:
diff --git a/manifests/namespace-install.yaml b/manifests/namespace-install.yaml
index 6c86d3700..3fdce41b9 100644
--- a/manifests/namespace-install.yaml
+++ b/manifests/namespace-install.yaml
@@ -1418,6 +1418,12 @@ spec:
               key: application.namespaces
               name: argocd-cmd-params-cm
               optional: true
+        - name: ARGOCD_SERVER_ENABLE_PROXY_EXTENSION
+          valueFrom:
+            configMapKeyRef:
+              key: server.enable.proxy.extension
+              name: argocd-cmd-params-cm
+              optional: true
         image: quay.io/argoproj/argocd:latest
         imagePullPolicy: Always
         livenessProbe:
diff --git a/pkg/apiclient/application/application.pb.go b/pkg/apiclient/application/application.pb.go
index 60f4b7aa4..0de79ae03 100644
--- a/pkg/apiclient/application/application.pb.go
+++ b/pkg/apiclient/application/application.pb.go
@@ -2411,6 +2411,179 @@ func (m *ManagedResourcesResponse) GetItems() []*v1alpha1.ResourceDiff {
 	return nil
 }
 
+type LinkInfo struct {
+	Title                *string  `protobuf:"bytes,1,req,name=title" json:"title,omitempty"`
+	Url                  *string  `protobuf:"bytes,2,req,name=url" json:"url,omitempty"`
+	Description          *string  `protobuf:"bytes,3,opt,name=description" json:"description,omitempty"`
+	IconClass            *string  `protobuf:"bytes,4,opt,name=iconClass" json:"iconClass,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *LinkInfo) Reset()         { *m = LinkInfo{} }
+func (m *LinkInfo) String() string { return proto.CompactTextString(m) }
+func (*LinkInfo) ProtoMessage()    {}
+func (*LinkInfo) Descriptor() ([]byte, []int) {
+	return fileDescriptor_df6e82b174b5eaec, []int{32}
+}
+func (m *LinkInfo) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *LinkInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	if deterministic {
+		return xxx_messageInfo_LinkInfo.Marshal(b, m, deterministic)
+	} else {
+		b = b[:cap(b)]
+		n, err := m.MarshalToSizedBuffer(b)
+		if err != nil {
+			return nil, err
+		}
+		return b[:n], nil
+	}
+}
+func (m *LinkInfo) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_LinkInfo.Merge(m, src)
+}
+func (m *LinkInfo) XXX_Size() int {
+	return m.Size()
+}
+func (m *LinkInfo) XXX_DiscardUnknown() {
+	xxx_messageInfo_LinkInfo.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_LinkInfo proto.InternalMessageInfo
+
+func (m *LinkInfo) GetTitle() string {
+	if m != nil && m.Title != nil {
+		return *m.Title
+	}
+	return ""
+}
+
+func (m *LinkInfo) GetUrl() string {
+	if m != nil && m.Url != nil {
+		return *m.Url
+	}
+	return ""
+}
+
+func (m *LinkInfo) GetDescription() string {
+	if m != nil && m.Description != nil {
+		return *m.Description
+	}
+	return ""
+}
+
+func (m *LinkInfo) GetIconClass() string {
+	if m != nil && m.IconClass != nil {
+		return *m.IconClass
+	}
+	return ""
+}
+
+type LinksResponse struct {
+	Items                []*LinkInfo `protobuf:"bytes,1,rep,name=items" json:"items,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}    `json:"-"`
+	XXX_unrecognized     []byte      `json:"-"`
+	XXX_sizecache        int32       `json:"-"`
+}
+
+func (m *LinksResponse) Reset()         { *m = LinksResponse{} }
+func (m *LinksResponse) String() string { return proto.CompactTextString(m) }
+func (*LinksResponse) ProtoMessage()    {}
+func (*LinksResponse) Descriptor() ([]byte, []int) {
+	return fileDescriptor_df6e82b174b5eaec, []int{33}
+}
+func (m *LinksResponse) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *LinksResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	if deterministic {
+		return xxx_messageInfo_LinksResponse.Marshal(b, m, deterministic)
+	} else {
+		b = b[:cap(b)]
+		n, err := m.MarshalToSizedBuffer(b)
+		if err != nil {
+			return nil, err
+		}
+		return b[:n], nil
+	}
+}
+func (m *LinksResponse) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_LinksResponse.Merge(m, src)
+}
+func (m *LinksResponse) XXX_Size() int {
+	return m.Size()
+}
+func (m *LinksResponse) XXX_DiscardUnknown() {
+	xxx_messageInfo_LinksResponse.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_LinksResponse proto.InternalMessageInfo
+
+func (m *LinksResponse) GetItems() []*LinkInfo {
+	if m != nil {
+		return m.Items
+	}
+	return nil
+}
+
+type ListAppLinksRequest struct {
+	Name                 *string  `protobuf:"bytes,1,req,name=name" json:"name,omitempty"`
+	Namespace            *string  `protobuf:"bytes,3,opt,name=namespace" json:"namespace,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *ListAppLinksRequest) Reset()         { *m = ListAppLinksRequest{} }
+func (m *ListAppLinksRequest) String() string { return proto.CompactTextString(m) }
+func (*ListAppLinksRequest) ProtoMessage()    {}
+func (*ListAppLinksRequest) Descriptor() ([]byte, []int) {
+	return fileDescriptor_df6e82b174b5eaec, []int{34}
+}
+func (m *ListAppLinksRequest) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *ListAppLinksRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	if deterministic {
+		return xxx_messageInfo_ListAppLinksRequest.Marshal(b, m, deterministic)
+	} else {
+		b = b[:cap(b)]
+		n, err := m.MarshalToSizedBuffer(b)
+		if err != nil {
+			return nil, err
+		}
+		return b[:n], nil
+	}
+}
+func (m *ListAppLinksRequest) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_ListAppLinksRequest.Merge(m, src)
+}
+func (m *ListAppLinksRequest) XXX_Size() int {
+	return m.Size()
+}
+func (m *ListAppLinksRequest) XXX_DiscardUnknown() {
+	xxx_messageInfo_ListAppLinksRequest.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_ListAppLinksRequest proto.InternalMessageInfo
+
+func (m *ListAppLinksRequest) GetName() string {
+	if m != nil && m.Name != nil {
+		return *m.Name
+	}
+	return ""
+}
+
+func (m *ListAppLinksRequest) GetNamespace() string {
+	if m != nil && m.Namespace != nil {
+		return *m.Namespace
+	}
+	return ""
+}
+
 func init() {
 	proto.RegisterType((*ApplicationQuery)(nil), "application.ApplicationQuery")
 	proto.RegisterType((*NodeQuery)(nil), "application.NodeQuery")
@@ -2444,6 +2617,9 @@ func init() {
 	proto.RegisterType((*OperationTerminateResponse)(nil), "application.OperationTerminateResponse")
 	proto.RegisterType((*ResourcesQuery)(nil), "application.ResourcesQuery")
 	proto.RegisterType((*ManagedResourcesResponse)(nil), "application.ManagedResourcesResponse")
+	proto.RegisterType((*LinkInfo)(nil), "application.LinkInfo")
+	proto.RegisterType((*LinksResponse)(nil), "application.LinksResponse")
+	proto.RegisterType((*ListAppLinksRequest)(nil), "application.ListAppLinksRequest")
 }
 
 func init() {
@@ -2451,160 +2627,169 @@ func init() {
 }
 
 var fileDescriptor_df6e82b174b5eaec = []byte{
-	// 2444 bytes of a gzipped FileDescriptorProto
+	// 2581 bytes of a gzipped FileDescriptorProto
 	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x5a, 0xcd, 0x8f, 0x1c, 0x47,
-	0x15, 0xa7, 0x66, 0xbf, 0x66, 0xde, 0xac, 0xbf, 0x2a, 0xf1, 0xd2, 0x19, 0xaf, 0xcd, 0xa6, 0xfd,
-	0xb5, 0x5e, 0x7b, 0x67, 0xec, 0xc1, 0x20, 0x67, 0x03, 0x02, 0xdb, 0xf1, 0x17, 0xac, 0x1d, 0xd3,
-	0x6b, 0xb3, 0x28, 0x1c, 0xa0, 0xd2, 0x5d, 0x3b, 0xdb, 0xec, 0x4c, 0x57, 0xbb, 0xbb, 0x67, 0xac,
-	0x91, 0xf1, 0x25, 0x08, 0x4e, 0x28, 0x48, 0x49, 0x0e, 0x28, 0x8a, 0x10, 0x4a, 0x94, 0x0b, 0x17,
-	0x6e, 0x08, 0x89, 0x0b, 0x5c, 0x10, 0x48, 0x1c, 0x10, 0x1f, 0x97, 0x9c, 0x90, 0xc5, 0x8d, 0x0b,
-	0x7f, 0x02, 0xaa, 0xea, 0xaa, 0xee, 0xea, 0x99, 0x9e, 0x9e, 0x59, 0x76, 0xa3, 0xf8, 0x56, 0xaf,
-	0xba, 0xea, 0xbd, 0x5f, 0xbd, 0x7a, 0xef, 0x55, 0xd5, 0x6f, 0x06, 0x4e, 0x85, 0x34, 0xe8, 0xd1,
-	0xa0, 0x41, 0x7c, 0xbf, 0xed, 0xda, 0x24, 0x72, 0x99, 0xa7, 0xb7, 0xeb, 0x7e, 0xc0, 0x22, 0x86,
-	0xab, 0x5a, 0x57, 0x6d, 0xb1, 0xc5, 0x58, 0xab, 0x4d, 0x1b, 0xc4, 0x77, 0x1b, 0xc4, 0xf3, 0x58,
-	0x24, 0xba, 0xc3, 0x78, 0x68, 0xcd, 0xdc, 0xb9, 0x12, 0xd6, 0x5d, 0x26, 0xbe, 0xda, 0x2c, 0xa0,
-	0x8d, 0xde, 0xa5, 0x46, 0x8b, 0x7a, 0x34, 0x20, 0x11, 0x75, 0xe4, 0x98, 0xcb, 0xe9, 0x98, 0x0e,
-	0xb1, 0xb7, 0x5d, 0x8f, 0x06, 0xfd, 0x86, 0xbf, 0xd3, 0xe2, 0x1d, 0x61, 0xa3, 0x43, 0x23, 0x92,
-	0x37, 0x6b, 0xbd, 0xe5, 0x46, 0xdb, 0xdd, 0x37, 0xeb, 0x36, 0xeb, 0x34, 0x48, 0xd0, 0x62, 0x7e,
-	0xc0, 0x7e, 0x20, 0x1a, 0xab, 0xb6, 0xd3, 0xe8, 0x35, 0x53, 0x05, 0xfa, 0x5a, 0x7a, 0x97, 0x48,
-	0xdb, 0xdf, 0x26, 0xc3, 0xda, 0x6e, 0x8c, 0xd1, 0x16, 0x50, 0x9f, 0x49, 0xdf, 0x88, 0xa6, 0x1b,
-	0xb1, 0xa0, 0xaf, 0x35, 0x63, 0x35, 0xe6, 0x27, 0x08, 0x0e, 0x5f, 0x4d, 0xed, 0x7d, 0xab, 0x4b,
-	0x83, 0x3e, 0xc6, 0x30, 0xed, 0x91, 0x0e, 0x35, 0xd0, 0x12, 0x5a, 0xae, 0x58, 0xa2, 0x8d, 0x0d,
-	0x98, 0x0b, 0xe8, 0x56, 0x40, 0xc3, 0x6d, 0xa3, 0x24, 0xba, 0x95, 0x88, 0x6b, 0x50, 0xe6, 0xc6,
-	0xa9, 0x1d, 0x85, 0xc6, 0xd4, 0xd2, 0xd4, 0x72, 0xc5, 0x4a, 0x64, 0xbc, 0x0c, 0x87, 0x02, 0x1a,
-	0xb2, 0x6e, 0x60, 0xd3, 0x6f, 0xd3, 0x20, 0x74, 0x99, 0x67, 0x4c, 0x8b, 0xd9, 0x83, 0xdd, 0x5c,
-	0x4b, 0x48, 0xdb, 0xd4, 0x8e, 0x58, 0x60, 0xcc, 0x88, 0x21, 0x89, 0xcc, 0xf1, 0x70, 0xe0, 0xc6,
-	0x6c, 0x8c, 0x87, 0xb7, 0xb1, 0x09, 0xf3, 0xc4, 0xf7, 0xef, 0x91, 0x0e, 0x0d, 0x7d, 0x62, 0x53,
-	0x63, 0x4e, 0x7c, 0xcb, 0xf4, 0x99, 0xd7, 0xa1, 0x72, 0x8f, 0x39, 0x74, 0xf4, 0xa2, 0x06, 0x95,
-	0x94, 0x72, 0x94, 0xec, 0xc0, 0x51, 0x8b, 0xf6, 0x5c, 0x0e, 0xf2, 0x2e, 0x8d, 0x88, 0x43, 0x22,
-	0x32, 0xa8, 0xb0, 0x94, 0x28, 0xac, 0x41, 0x39, 0x90, 0x83, 0x8d, 0x92, 0xe8, 0x4f, 0xe4, 0x21,
-	0x63, 0x53, 0x39, 0xc6, 0xfe, 0x82, 0xe0, 0x84, 0xb6, 0x1d, 0x96, 0x74, 0xd2, 0x8d, 0x1e, 0xf5,
-	0xa2, 0x70, 0xb4, 0xd9, 0x0b, 0x70, 0x44, 0xf9, 0x73, 0x70, 0x31, 0xc3, 0x1f, 0x38, 0x10, 0xbd,
-	0x53, 0x01, 0xd1, 0xfb, 0xf0, 0x12, 0x54, 0x95, 0xfc, 0xf0, 0xce, 0x6b, 0x72, 0xd3, 0xf4, 0xae,
-	0xa1, 0xe5, 0xcc, 0xe4, 0x2c, 0xc7, 0x03, 0x43, 0x5b, 0xcd, 0x5d, 0xe2, 0xb9, 0x5b, 0x34, 0x8c,
-	0x26, 0x75, 0x1f, 0xda, 0xb5, 0xfb, 0x5e, 0x86, 0xca, 0x4d, 0xb7, 0x4d, 0xaf, 0x6f, 0x77, 0xbd,
-	0x1d, 0xfc, 0x22, 0xcc, 0xd8, 0xbc, 0x21, 0x2c, 0xcc, 0x5b, 0xb1, 0x60, 0x3e, 0x86, 0x97, 0x47,
-	0x41, 0xda, 0x74, 0xa3, 0x6d, 0x3e, 0x3d, 0x1c, 0x85, 0xcd, 0xde, 0xa6, 0xf6, 0x4e, 0xd8, 0xed,
-	0xa8, 0xad, 0x55, 0xf2, 0x44, 0xd8, 0x7e, 0x85, 0x60, 0x79, 0xac, 0xe5, 0xcd, 0x80, 0xf8, 0x3e,
-	0x0d, 0xf0, 0x4d, 0x98, 0x79, 0xc4, 0x3f, 0x88, 0x68, 0xad, 0x36, 0xeb, 0x75, 0xbd, 0xa6, 0x8d,
-	0xd5, 0x72, 0xfb, 0x73, 0x56, 0x3c, 0x1d, 0xd7, 0x95, 0x0f, 0x4a, 0x42, 0xcf, 0x42, 0x46, 0x4f,
-	0xe2, 0x2a, 0x3e, 0x5e, 0x0c, 0xbb, 0x36, 0x0b, 0xd3, 0x3e, 0x09, 0x22, 0xf3, 0x28, 0xbc, 0x90,
-	0x0d, 0x43, 0x9f, 0x79, 0x21, 0x35, 0x7f, 0x87, 0x32, 0x1b, 0x7a, 0x3d, 0xa0, 0x24, 0xa2, 0x16,
-	0x7d, 0xd4, 0xa5, 0x61, 0x84, 0x77, 0x40, 0x2f, 0xb3, 0xc2, 0x77, 0xd5, 0xe6, 0x9d, 0x7a, 0x5a,
-	0xa7, 0xea, 0xaa, 0x4e, 0x89, 0xc6, 0xf7, 0x6c, 0xa7, 0xde, 0x6b, 0xd6, 0xfd, 0x9d, 0x56, 0x9d,
-	0x57, 0xbd, 0x0c, 0x32, 0x55, 0xf5, 0xf4, 0xa5, 0x5a, 0xba, 0x76, 0xbc, 0x00, 0xb3, 0x5d, 0x3f,
-	0xa4, 0x41, 0x24, 0x56, 0x56, 0xb6, 0xa4, 0xc4, 0x77, 0xa9, 0x47, 0xda, 0xae, 0x43, 0xa2, 0x78,
-	0x17, 0xca, 0x56, 0x22, 0x9b, 0x1f, 0x65, 0xd1, 0x3f, 0xf4, 0x9d, 0xcf, 0x0a, 0xbd, 0x8e, 0xb2,
-	0x34, 0x80, 0xf2, 0xfd, 0x2c, 0xca, 0xd7, 0x68, 0x9b, 0xa6, 0x28, 0xf3, 0x02, 0xd3, 0x80, 0x39,
-	0x9b, 0x84, 0x36, 0x71, 0x94, 0x2e, 0x25, 0xf2, 0xb2, 0xe0, 0x07, 0xcc, 0x27, 0x2d, 0xa1, 0xe9,
-	0x3e, 0x6b, 0xbb, 0x76, 0x5f, 0xc6, 0xe6, 0xf0, 0x87, 0xa1, 0x20, 0x9e, 0xce, 0x09, 0xe2, 0x93,
-	0x50, 0xdd, 0xe8, 0x7b, 0xf6, 0xeb, 0xbe, 0x38, 0x32, 0x79, 0x8a, 0xb9, 0x11, 0xed, 0x84, 0x06,
-	0x12, 0x75, 0x3f, 0x16, 0xcc, 0x0f, 0x66, 0x60, 0x41, 0x5b, 0x01, 0x9f, 0x50, 0x84, 0xbf, 0x28,
-	0xe9, 0x17, 0x60, 0xd6, 0x09, 0xfa, 0x56, 0xd7, 0x93, 0x9b, 0x29, 0x25, 0x6e, 0xd8, 0x0f, 0xba,
-	0x5e, 0x0c, 0xb2, 0x6c, 0xc5, 0x02, 0xde, 0x82, 0x72, 0x18, 0xf1, 0x43, 0xb2, 0xd5, 0x17, 0xe5,
-	0xa8, 0xda, 0xfc, 0xc6, 0xde, 0x36, 0x90, 0x43, 0xdf, 0x90, 0x1a, 0xad, 0x44, 0x37, 0x7e, 0x04,
-	0x15, 0x55, 0x09, 0x43, 0x63, 0x6e, 0x69, 0x6a, 0xb9, 0xda, 0xdc, 0xd8, 0xbb, 0xa1, 0xd7, 0x7d,
-	0x7e, 0xc0, 0x6b, 0x55, 0xdf, 0x4a, 0xad, 0xe0, 0x45, 0xa8, 0x74, 0x64, 0xae, 0x87, 0x46, 0x59,
-	0x78, 0x3b, 0xed, 0xc0, 0xdf, 0x81, 0x19, 0xd7, 0xdb, 0x62, 0xa1, 0x51, 0x11, 0x60, 0xae, 0xed,
-	0x0d, 0xcc, 0x1d, 0x6f, 0x8b, 0x59, 0xb1, 0x42, 0xfc, 0x08, 0x0e, 0x04, 0x34, 0x0a, 0xfa, 0xca,
-	0x0b, 0x06, 0x08, 0xbf, 0x7e, 0x73, 0x6f, 0x16, 0x2c, 0x5d, 0xa5, 0x95, 0xb5, 0x80, 0xd7, 0xa0,
-	0x1a, 0xa6, 0x31, 0x66, 0x54, 0x85, 0x41, 0x23, 0xa3, 0x48, 0x8b, 0x41, 0x4b, 0x1f, 0x3c, 0x14,
-	0xc3, 0xf3, 0x39, 0x31, 0xfc, 0x4f, 0x04, 0x8b, 0x43, 0x65, 0x60, 0xc3, 0xa7, 0x85, 0x41, 0x4a,
-	0x60, 0x3a, 0xf4, 0xa9, 0x2d, 0x2a, 0x7f, 0xb5, 0x79, 0x77, 0xdf, 0xea, 0x82, 0xb0, 0x2b, 0x54,
-	0x17, 0x95, 0xae, 0x89, 0x72, 0xf3, 0xc7, 0x08, 0x3e, 0xaf, 0x69, 0xbe, 0x4f, 0x22, 0x7b, 0xbb,
-	0x68, 0x49, 0x3c, 0x87, 0xf8, 0x18, 0x79, 0x9a, 0xc5, 0x02, 0x0f, 0x34, 0xd1, 0x78, 0xd0, 0xf7,
-	0x39, 0x0c, 0xfe, 0x25, 0xed, 0x98, 0xe8, 0xd0, 0x7f, 0x07, 0x41, 0x4d, 0xaf, 0x7c, 0xac, 0xdd,
-	0x7e, 0x93, 0xd8, 0x3b, 0x45, 0x50, 0x0e, 0x42, 0xc9, 0x75, 0x04, 0x8e, 0x29, 0xab, 0xe4, 0x3a,
-	0xbb, 0x4c, 0xfb, 0x41, 0x50, 0xb3, 0x39, 0xa0, 0x3e, 0x19, 0x00, 0xa5, 0x52, 0xac, 0x00, 0xd4,
-	0x22, 0x54, 0xbc, 0x81, 0xcb, 0x54, 0xda, 0x91, 0x73, 0x89, 0x2a, 0x0d, 0x5d, 0xa2, 0x0c, 0x98,
-	0xeb, 0x25, 0xb7, 0x5e, 0xfe, 0x59, 0x89, 0x7c, 0x21, 0xad, 0x80, 0x75, 0x7d, 0xe9, 0xc0, 0x58,
-	0xe0, 0x28, 0x76, 0x5c, 0xcf, 0x31, 0x66, 0x63, 0x14, 0xbc, 0x3d, 0xd1, 0x3d, 0xf7, 0xdd, 0x12,
-	0x7c, 0x21, 0x67, 0x71, 0x63, 0x23, 0xe0, 0xf9, 0x58, 0x61, 0x12, 0x87, 0x73, 0x23, 0xe3, 0xb0,
-	0x3c, 0x2e, 0x0e, 0x2b, 0x39, 0x5e, 0x79, 0xbb, 0x04, 0x4b, 0x39, 0x5e, 0x19, 0x7f, 0xa0, 0x3e,
-	0x37, 0x6e, 0xd9, 0x62, 0x81, 0xdc, 0xf1, 0xb2, 0x15, 0x0b, 0x3c, 0x33, 0x58, 0xe0, 0x6f, 0x13,
-	0xcf, 0x28, 0xc7, 0x99, 0x11, 0x4b, 0x13, 0x39, 0xe4, 0xbf, 0x08, 0x0c, 0xe5, 0x85, 0xab, 0xb6,
-	0xf0, 0x49, 0xd7, 0x7b, 0xfe, 0x1d, 0xb1, 0x00, 0xb3, 0x44, 0xa0, 0x95, 0x01, 0x22, 0xa5, 0xa1,
-	0x25, 0x97, 0xf3, 0x6b, 0xe2, 0xb1, 0xec, 0x92, 0xc3, 0x75, 0x37, 0x8c, 0xd4, 0x85, 0x16, 0x6f,
-	0xc1, 0x5c, 0xac, 0x2d, 0xbe, 0xc2, 0x54, 0x9b, 0xeb, 0x7b, 0x3d, 0xd8, 0x32, 0xee, 0x55, 0xca,
-	0xcd, 0x57, 0xe0, 0x58, 0x6e, 0xf5, 0x91, 0x30, 0x6a, 0x50, 0x56, 0x87, 0xb9, 0xdc, 0x80, 0x44,
-	0x36, 0xff, 0x33, 0x95, 0x2d, 0xeb, 0xcc, 0x59, 0x67, 0xad, 0x82, 0xb7, 0x60, 0xf1, 0xa6, 0x19,
-	0x30, 0xe7, 0x33, 0x47, 0x7b, 0xf6, 0x29, 0x91, 0xcf, 0xb3, 0x99, 0x17, 0x11, 0xd7, 0xa3, 0x81,
-	0x3c, 0x5f, 0xd2, 0x0e, 0xee, 0xec, 0xd0, 0xf5, 0x6c, 0xba, 0x41, 0x6d, 0xe6, 0x39, 0xa1, 0xd8,
-	0xb5, 0x29, 0x2b, 0xd3, 0x87, 0x6f, 0x43, 0x45, 0xc8, 0x0f, 0xdc, 0x4e, 0x5c, 0x84, 0xab, 0xcd,
-	0x95, 0x7a, 0x4c, 0x95, 0xd4, 0x75, 0xaa, 0x24, 0xf5, 0x61, 0x87, 0x46, 0xa4, 0xde, 0xbb, 0x54,
-	0xe7, 0x33, 0xac, 0x74, 0x32, 0xc7, 0x12, 0x11, 0xb7, 0xbd, 0xee, 0x7a, 0xe2, 0x82, 0xc5, 0x4d,
-	0xa5, 0x1d, 0x3c, 0x20, 0xb6, 0x58, 0xbb, 0xcd, 0x1e, 0xab, 0x1c, 0x88, 0x25, 0x3e, 0xab, 0xeb,
-	0x45, 0x6e, 0x5b, 0xd8, 0x8f, 0x13, 0x20, 0xed, 0x10, 0xb3, 0xdc, 0x76, 0x44, 0x03, 0x71, 0x85,
-	0xa9, 0x58, 0x52, 0x4a, 0x42, 0xae, 0x1a, 0xf3, 0x02, 0x2a, 0xf7, 0xe2, 0xe0, 0x9c, 0xd7, 0x83,
-	0x73, 0x30, 0xe0, 0x0f, 0xe4, 0xbc, 0x9b, 0x05, 0x19, 0x42, 0x7b, 0x2e, 0xeb, 0x86, 0xc6, 0xc1,
-	0xf8, 0x10, 0x57, 0xf2, 0x50, 0xc0, 0x1e, 0xca, 0x09, 0xd8, 0xdf, 0x23, 0x28, 0xaf, 0xb3, 0xd6,
-	0x0d, 0x2f, 0x0a, 0xfa, 0xe2, 0x66, 0xcf, 0xbc, 0x88, 0x7a, 0x2a, 0x2a, 0x94, 0xc8, 0x5d, 0x1d,
-	0xb9, 0x1d, 0xba, 0x11, 0x91, 0x8e, 0x2f, 0xef, 0x24, 0xbb, 0x72, 0x75, 0x32, 0x99, 0x2f, 0xbf,
-	0x4d, 0xc2, 0x48, 0x64, 0x6f, 0xd9, 0x12, 0x6d, 0x0e, 0x34, 0x19, 0xb0, 0x11, 0x05, 0x32, 0x75,
-	0x33, 0x7d, 0x7a, 0x20, 0xcd, 0xc4, 0xd8, 0xa4, 0x68, 0x6e, 0xc0, 0x4b, 0xc9, 0x55, 0xf6, 0x01,
-	0x0d, 0x3a, 0xae, 0x47, 0x8a, 0xeb, 0xed, 0x24, 0x2c, 0xcc, 0xc3, 0x4c, 0x02, 0xf1, 0xfb, 0xdf,
-	0xa6, 0xeb, 0x39, 0xec, 0x71, 0x41, 0x22, 0x4c, 0xa2, 0xf6, 0x6f, 0x59, 0xbe, 0x45, 0xd3, 0x9b,
-	0xe4, 0xe6, 0x6d, 0x38, 0xc0, 0xb3, 0xb8, 0x47, 0xe5, 0x07, 0x59, 0x28, 0xcc, 0x51, 0x4f, 0xf2,
-	0x54, 0x87, 0x95, 0x9d, 0x88, 0xd7, 0xe1, 0x10, 0x09, 0x43, 0xb7, 0xe5, 0x51, 0x47, 0xe9, 0x2a,
-	0x4d, 0xac, 0x6b, 0x70, 0x6a, 0xfc, 0xec, 0x13, 0x23, 0xe4, 0xde, 0x29, 0xd1, 0xfc, 0x11, 0x82,
-	0xa3, 0xb9, 0x4a, 0x92, 0x58, 0x47, 0x5a, 0x79, 0xad, 0x41, 0x39, 0xb4, 0xb7, 0xa9, 0xd3, 0x6d,
-	0x53, 0xc5, 0x6b, 0x28, 0x99, 0x7f, 0x73, 0xba, 0xf1, 0x4e, 0xca, 0xf2, 0x9e, 0xc8, 0xf8, 0x04,
-	0x40, 0x87, 0x78, 0x5d, 0xd2, 0x16, 0x10, 0xa6, 0x05, 0x04, 0xad, 0xc7, 0x5c, 0x84, 0x5a, 0x5e,
-	0x18, 0x48, 0x26, 0xe1, 0x1f, 0x08, 0x0e, 0xaa, 0x32, 0x28, 0xf7, 0x70, 0x19, 0x0e, 0x69, 0x6e,
-	0xb8, 0x97, 0x6e, 0xe7, 0x60, 0xf7, 0x98, 0x12, 0xa7, 0x62, 0x61, 0x2a, 0xcb, 0x5e, 0xf6, 0x32,
-	0xfc, 0xe3, 0xc4, 0xe7, 0x10, 0xda, 0xd5, 0x4d, 0xec, 0x87, 0x60, 0xdc, 0x25, 0x1e, 0x69, 0x51,
-	0x27, 0x59, 0x5c, 0x12, 0x48, 0xdf, 0xd7, 0x1f, 0xcb, 0x7b, 0x7e, 0x9a, 0x26, 0xd7, 0x19, 0x77,
-	0x6b, 0x4b, 0x3e, 0xbc, 0x9b, 0x3f, 0x59, 0x02, 0xac, 0x6f, 0x3c, 0x0d, 0x7a, 0xae, 0x4d, 0xf1,
-	0x3b, 0x08, 0xa6, 0xf9, 0xa9, 0x87, 0x8f, 0x8f, 0x8a, 0x33, 0xb1, 0x01, 0xb5, 0xfd, 0x7b, 0xd5,
-	0x70, 0x6b, 0xe6, 0xe2, 0x5b, 0x7f, 0xff, 0xf7, 0xbb, 0xa5, 0x05, 0xfc, 0xa2, 0xe0, 0xd2, 0x7b,
-	0x97, 0x74, 0x5e, 0x3b, 0xc4, 0x3f, 0x45, 0x80, 0xe5, 0x51, 0xac, 0x51, 0x9c, 0xf8, 0xfc, 0x28,
-	0x88, 0x39, 0x54, 0x68, 0xed, 0xb8, 0x56, 0xf2, 0xea, 0x36, 0x0b, 0x28, 0x2f, 0x70, 0x62, 0x80,
-	0x00, 0xb0, 0x22, 0x00, 0x9c, 0xc2, 0x66, 0x1e, 0x80, 0xc6, 0x13, 0x1e, 0x18, 0x4f, 0x1b, 0x34,
-	0xb6, 0xfb, 0x21, 0x82, 0x99, 0x4d, 0x71, 0xf1, 0x1c, 0xe3, 0xa4, 0x8d, 0x7d, 0x73, 0x92, 0x30,
-	0x27, 0xd0, 0x9a, 0x27, 0x05, 0xd2, 0xe3, 0xf8, 0x98, 0x42, 0x1a, 0x46, 0x01, 0x25, 0x9d, 0x0c,
-	0xe0, 0x8b, 0x08, 0x7f, 0x8c, 0x60, 0x36, 0xe6, 0xdc, 0xf0, 0xe9, 0x51, 0x28, 0x33, 0x9c, 0x5c,
-	0x6d, 0xff, 0x08, 0x2c, 0xf3, 0x9c, 0xc0, 0x78, 0xd2, 0xcc, 0xdd, 0xce, 0xb5, 0x0c, 0xbd, 0xf5,
-	0x1e, 0x82, 0xa9, 0x5b, 0x74, 0x6c, 0xbc, 0xed, 0x23, 0xb8, 0x21, 0x07, 0xe6, 0x6c, 0x35, 0xfe,
-	0x08, 0xc1, 0x4b, 0xb7, 0x68, 0x94, 0x5f, 0xef, 0xf1, 0xf2, 0xf8, 0x22, 0x2c, 0xc3, 0xee, 0xfc,
-	0x04, 0x23, 0x93, 0x42, 0xd7, 0x10, 0xc8, 0xce, 0xe1, 0xb3, 0x45, 0x41, 0x18, 0xf6, 0x3d, 0xfb,
-	0xb1, 0xc4, 0xf1, 0x67, 0x04, 0x87, 0x07, 0x7f, 0x70, 0xc0, 0xd9, 0x13, 0x22, 0xf7, 0xf7, 0x88,
-	0xda, 0xbd, 0xbd, 0x16, 0x94, 0xac, 0x52, 0xf3, 0xaa, 0x40, 0xfe, 0x2a, 0x7e, 0xa5, 0x08, 0xb9,
-	0x62, 0xea, 0xc2, 0xc6, 0x13, 0xd5, 0x7c, 0x2a, 0x7e, 0x01, 0x13, 0xb0, 0xdf, 0x42, 0x30, 0x7f,
-	0x8b, 0x46, 0x77, 0x13, 0xa2, 0xea, 0xf4, 0x44, 0x44, 0x76, 0x6d, 0xb1, 0xae, 0xfd, 0x50, 0xa5,
-	0x3e, 0x25, 0x2e, 0x5d, 0x15, 0xc0, 0xce, 0xe2, 0xd3, 0x45, 0xc0, 0x52, 0x72, 0xec, 0x43, 0x04,
-	0x47, 0x75, 0x10, 0x29, 0xcd, 0xff, 0xa5, 0xdd, 0xd1, 0xea, 0x92, 0x9c, 0x1f, 0x83, 0xae, 0x29,
-	0xd0, 0x5d, 0x30, 0xf3, 0x37, 0xbc, 0x33, 0x84, 0x62, 0x0d, 0xad, 0x2c, 0x23, 0xfc, 0x07, 0x04,
-	0xb3, 0x31, 0x13, 0x35, 0xda, 0x47, 0x19, 0xc2, 0x7a, 0x3f, 0xb3, 0xe7, 0x86, 0x80, 0xfc, 0xb5,
-	0xda, 0xc5, 0x7c, 0x87, 0xea, 0xf3, 0xd5, 0xd6, 0xd6, 0x85, 0x97, 0xb3, 0x69, 0xff, 0x1b, 0x04,
-	0x90, 0xb2, 0x69, 0xf8, 0x5c, 0xf1, 0x3a, 0x34, 0xc6, 0xad, 0xb6, 0xbf, 0x7c, 0x9a, 0x59, 0x17,
-	0xeb, 0x59, 0xae, 0x2d, 0x15, 0xe6, 0x9c, 0x4f, 0xed, 0xb5, 0x98, 0x79, 0xfb, 0x25, 0x82, 0x19,
-	0x41, 0x96, 0xe0, 0x53, 0xa3, 0x30, 0xeb, 0x5c, 0xca, 0x7e, 0xba, 0xfe, 0x8c, 0x80, 0xba, 0xd4,
-	0x2c, 0x2a, 0x5c, 0x6b, 0x68, 0x05, 0xf7, 0x60, 0x36, 0x26, 0x2e, 0x46, 0x87, 0x47, 0x86, 0xd8,
-	0xa8, 0x2d, 0x15, 0x1c, 0xa4, 0x71, 0xa0, 0xca, 0x9a, 0xb9, 0x32, 0xae, 0x66, 0x4e, 0xf3, 0xb2,
-	0x86, 0x4f, 0x16, 0x15, 0xbd, 0x4f, 0xc1, 0x31, 0xe7, 0x05, 0xba, 0xd3, 0xe6, 0xd2, 0xb8, 0xba,
-	0xc9, 0xbd, 0xf3, 0x73, 0x04, 0x87, 0x07, 0xef, 0x5d, 0xf8, 0xd8, 0x40, 0xcd, 0xd4, 0x2f, 0x9b,
-	0xb5, 0xac, 0x17, 0x47, 0xdd, 0xd9, 0xcc, 0xaf, 0x0b, 0x14, 0x6b, 0xf8, 0xca, 0xd8, 0xcc, 0xb8,
-	0xa7, 0xaa, 0x0e, 0x57, 0xb4, 0x9a, 0x12, 0xf7, 0xbf, 0x45, 0x30, 0xaf, 0xf4, 0x3e, 0x08, 0x28,
-	0x2d, 0x86, 0xb5, 0x7f, 0x89, 0xc0, 0x6d, 0x99, 0x5f, 0x11, 0xf0, 0xbf, 0x8c, 0x2f, 0x4f, 0x08,
-	0x5f, 0xc1, 0x5e, 0x8d, 0x38, 0xd2, 0x3f, 0x22, 0x38, 0xb2, 0x19, 0xc7, 0xfd, 0x67, 0x84, 0xff,
-	0xba, 0xc0, 0xff, 0x55, 0xfc, 0x6a, 0xc1, 0xbd, 0x68, 0xdc, 0x32, 0x2e, 0x22, 0xfc, 0x6b, 0x04,
-	0x65, 0x45, 0x43, 0xe3, 0xb3, 0x23, 0x13, 0x23, 0x4b, 0x54, 0xef, 0x67, 0x30, 0xcb, 0x4b, 0x80,
-	0x79, 0xaa, 0xf0, 0x28, 0x95, 0xf6, 0x79, 0x40, 0xbf, 0x87, 0x00, 0x27, 0x8f, 0xa6, 0xe4, 0x19,
-	0x85, 0xcf, 0x64, 0x4c, 0x8d, 0x7c, 0x65, 0xd7, 0xce, 0x8e, 0x1d, 0x97, 0x3d, 0x4a, 0x57, 0x0a,
-	0x8f, 0x52, 0x96, 0xd8, 0x7f, 0x1b, 0x41, 0xf5, 0x16, 0x4d, 0xee, 0xec, 0x05, 0xbe, 0xcc, 0xf2,
-	0xeb, 0xb5, 0xe5, 0xf1, 0x03, 0x25, 0xa2, 0x0b, 0x02, 0xd1, 0x19, 0x5c, 0xec, 0x2a, 0x05, 0xe0,
-	0x03, 0x04, 0x07, 0xee, 0xeb, 0x21, 0x8a, 0x2f, 0x8c, 0xb3, 0x94, 0xa9, 0xe4, 0x93, 0xe3, 0xfa,
-	0xa2, 0xc0, 0xb5, 0x6a, 0x4e, 0x84, 0x6b, 0x4d, 0x92, 0xd8, 0xbf, 0x40, 0xf0, 0x82, 0xfe, 0xc8,
-	0x91, 0x14, 0xe4, 0xff, 0xeb, 0xb7, 0x02, 0x26, 0xd3, 0xbc, 0x2c, 0xf0, 0xd5, 0xf1, 0x85, 0x49,
-	0xf0, 0x35, 0x24, 0x2f, 0x89, 0xdf, 0x47, 0x70, 0x44, 0x90, 0xc0, 0xba, 0xe2, 0x81, 0x23, 0x66,
-	0x14, 0x65, 0x3c, 0xc1, 0x11, 0x23, 0xeb, 0x8f, 0xb9, 0x2b, 0x50, 0x6b, 0x8a, 0xe0, 0xfd, 0x19,
-	0x82, 0x83, 0xea, 0x50, 0x93, 0xbb, 0xbb, 0x3a, 0xce, 0x71, 0xbb, 0x3d, 0x04, 0x65, 0xb8, 0xad,
-	0x4c, 0x16, 0x6e, 0x1f, 0x23, 0x98, 0x93, 0x04, 0x6c, 0xc1, 0x55, 0x41, 0x63, 0x68, 0x6b, 0x47,
-	0x33, 0xa3, 0x14, 0xb3, 0x67, 0x7e, 0x57, 0x98, 0x7d, 0x88, 0x1b, 0x45, 0x66, 0x7d, 0xe6, 0x84,
-	0x8d, 0x27, 0x92, 0x56, 0x7b, 0xda, 0x68, 0xb3, 0x56, 0xf8, 0x86, 0x89, 0x0b, 0x0f, 0x44, 0x3e,
-	0xe6, 0x22, 0xba, 0x76, 0xf3, 0x4f, 0xcf, 0x4e, 0xa0, 0xbf, 0x3e, 0x3b, 0x81, 0xfe, 0xf5, 0xec,
-	0x04, 0x7a, 0xe3, 0xca, 0x64, 0x7f, 0x3c, 0xb3, 0xdb, 0x2e, 0xf5, 0x22, 0x5d, 0xed, 0xff, 0x02,
-	0x00, 0x00, 0xff, 0xff, 0xa8, 0x3a, 0x7a, 0xdf, 0x5e, 0x27, 0x00, 0x00,
+	0x15, 0xa7, 0x66, 0xbf, 0x66, 0xde, 0xac, 0xbf, 0x2a, 0xf1, 0xd2, 0x69, 0xaf, 0xcd, 0xba, 0xfd,
+	0xb5, 0x5e, 0x7b, 0x67, 0xec, 0xc1, 0x20, 0x67, 0x13, 0x04, 0xb6, 0xe3, 0x2f, 0x58, 0x3b, 0xa6,
+	0xd7, 0xc6, 0x28, 0x1c, 0xa0, 0xd2, 0x53, 0x3b, 0xdb, 0x6c, 0x4f, 0x77, 0xbb, 0xbb, 0x67, 0xac,
+	0x91, 0xf1, 0x25, 0x88, 0x13, 0x51, 0x90, 0x92, 0x1c, 0x50, 0x14, 0x21, 0x94, 0x28, 0x17, 0x2e,
+	0xdc, 0x10, 0x12, 0x17, 0xb8, 0x20, 0x90, 0x38, 0x20, 0x3e, 0x2e, 0x39, 0x21, 0x8b, 0x1b, 0x17,
+	0xfe, 0x04, 0x54, 0xd5, 0x55, 0xdd, 0xd5, 0x33, 0x3d, 0x3d, 0xbd, 0xec, 0x46, 0xf1, 0xad, 0x5e,
+	0x4d, 0xd5, 0x7b, 0xbf, 0x7a, 0xf5, 0xbe, 0xea, 0xf5, 0xc0, 0xc9, 0x90, 0x06, 0x7d, 0x1a, 0x34,
+	0x89, 0xef, 0x3b, 0xb6, 0x45, 0x22, 0xdb, 0x73, 0xd5, 0x71, 0xc3, 0x0f, 0xbc, 0xc8, 0xc3, 0x75,
+	0x65, 0x4a, 0x5f, 0xec, 0x78, 0x5e, 0xc7, 0xa1, 0x4d, 0xe2, 0xdb, 0x4d, 0xe2, 0xba, 0x5e, 0xc4,
+	0xa7, 0xc3, 0x78, 0xa9, 0x6e, 0x6c, 0x5f, 0x0e, 0x1b, 0xb6, 0xc7, 0x7f, 0xb5, 0xbc, 0x80, 0x36,
+	0xfb, 0x17, 0x9b, 0x1d, 0xea, 0xd2, 0x80, 0x44, 0xb4, 0x2d, 0xd6, 0x5c, 0x4a, 0xd7, 0x74, 0x89,
+	0xb5, 0x65, 0xbb, 0x34, 0x18, 0x34, 0xfd, 0xed, 0x0e, 0x9b, 0x08, 0x9b, 0x5d, 0x1a, 0x91, 0xbc,
+	0x5d, 0xeb, 0x1d, 0x3b, 0xda, 0xea, 0xbd, 0xd9, 0xb0, 0xbc, 0x6e, 0x93, 0x04, 0x1d, 0xcf, 0x0f,
+	0xbc, 0x1f, 0xf2, 0xc1, 0xaa, 0xd5, 0x6e, 0xf6, 0x5b, 0x29, 0x03, 0xf5, 0x2c, 0xfd, 0x8b, 0xc4,
+	0xf1, 0xb7, 0xc8, 0x28, 0xb7, 0xeb, 0x13, 0xb8, 0x05, 0xd4, 0xf7, 0x84, 0x6e, 0xf8, 0xd0, 0x8e,
+	0xbc, 0x60, 0xa0, 0x0c, 0x63, 0x36, 0xc6, 0xa7, 0x08, 0x0e, 0x5e, 0x49, 0xe5, 0x7d, 0xbb, 0x47,
+	0x83, 0x01, 0xc6, 0x30, 0xed, 0x92, 0x2e, 0xd5, 0xd0, 0x12, 0x5a, 0xae, 0x99, 0x7c, 0x8c, 0x35,
+	0x98, 0x0b, 0xe8, 0x66, 0x40, 0xc3, 0x2d, 0xad, 0xc2, 0xa7, 0x25, 0x89, 0x75, 0xa8, 0x32, 0xe1,
+	0xd4, 0x8a, 0x42, 0x6d, 0x6a, 0x69, 0x6a, 0xb9, 0x66, 0x26, 0x34, 0x5e, 0x86, 0x03, 0x01, 0x0d,
+	0xbd, 0x5e, 0x60, 0xd1, 0xef, 0xd0, 0x20, 0xb4, 0x3d, 0x57, 0x9b, 0xe6, 0xbb, 0x87, 0xa7, 0x19,
+	0x97, 0x90, 0x3a, 0xd4, 0x8a, 0xbc, 0x40, 0x9b, 0xe1, 0x4b, 0x12, 0x9a, 0xe1, 0x61, 0xc0, 0xb5,
+	0xd9, 0x18, 0x0f, 0x1b, 0x63, 0x03, 0xe6, 0x89, 0xef, 0xdf, 0x25, 0x5d, 0x1a, 0xfa, 0xc4, 0xa2,
+	0xda, 0x1c, 0xff, 0x2d, 0x33, 0x67, 0x5c, 0x83, 0xda, 0x5d, 0xaf, 0x4d, 0xc7, 0x1f, 0x6a, 0x98,
+	0x49, 0x25, 0x87, 0xc9, 0x36, 0x1c, 0x36, 0x69, 0xdf, 0x66, 0x20, 0xef, 0xd0, 0x88, 0xb4, 0x49,
+	0x44, 0x86, 0x19, 0x56, 0x12, 0x86, 0x3a, 0x54, 0x03, 0xb1, 0x58, 0xab, 0xf0, 0xf9, 0x84, 0x1e,
+	0x11, 0x36, 0x95, 0x23, 0xec, 0x2f, 0x08, 0x8e, 0x29, 0xd7, 0x61, 0x0a, 0x25, 0x5d, 0xef, 0x53,
+	0x37, 0x0a, 0xc7, 0x8b, 0x3d, 0x0f, 0x87, 0xa4, 0x3e, 0x87, 0x0f, 0x33, 0xfa, 0x03, 0x03, 0xa2,
+	0x4e, 0x4a, 0x20, 0xea, 0x1c, 0x5e, 0x82, 0xba, 0xa4, 0x1f, 0xdc, 0x7e, 0x4d, 0x5c, 0x9a, 0x3a,
+	0x35, 0x72, 0x9c, 0x99, 0x9c, 0xe3, 0xb8, 0xa0, 0x29, 0xa7, 0xb9, 0x43, 0x5c, 0x7b, 0x93, 0x86,
+	0x51, 0x59, 0xf5, 0xa1, 0x1d, 0xab, 0xef, 0x38, 0xd4, 0x6e, 0xd8, 0x0e, 0xbd, 0xb6, 0xd5, 0x73,
+	0xb7, 0xf1, 0x8b, 0x30, 0x63, 0xb1, 0x01, 0x97, 0x30, 0x6f, 0xc6, 0x84, 0xf1, 0x18, 0x8e, 0x8f,
+	0x83, 0xf4, 0xd0, 0x8e, 0xb6, 0xd8, 0xf6, 0x70, 0x1c, 0x36, 0x6b, 0x8b, 0x5a, 0xdb, 0x61, 0xaf,
+	0x2b, 0xaf, 0x56, 0xd2, 0xa5, 0xb0, 0xfd, 0x0a, 0xc1, 0xf2, 0x44, 0xc9, 0x0f, 0x03, 0xe2, 0xfb,
+	0x34, 0xc0, 0x37, 0x60, 0xe6, 0x11, 0xfb, 0x81, 0x5b, 0x6b, 0xbd, 0xd5, 0x68, 0xa8, 0x31, 0x6d,
+	0x22, 0x97, 0x5b, 0x5f, 0x30, 0xe3, 0xed, 0xb8, 0x21, 0x75, 0x50, 0xe1, 0x7c, 0x16, 0x32, 0x7c,
+	0x12, 0x55, 0xb1, 0xf5, 0x7c, 0xd9, 0xd5, 0x59, 0x98, 0xf6, 0x49, 0x10, 0x19, 0x87, 0xe1, 0x85,
+	0xac, 0x19, 0xfa, 0x9e, 0x1b, 0x52, 0xe3, 0x77, 0x28, 0x73, 0xa1, 0xd7, 0x02, 0x4a, 0x22, 0x6a,
+	0xd2, 0x47, 0x3d, 0x1a, 0x46, 0x78, 0x1b, 0xd4, 0x30, 0xcb, 0x75, 0x57, 0x6f, 0xdd, 0x6e, 0xa4,
+	0x71, 0xaa, 0x21, 0xe3, 0x14, 0x1f, 0x7c, 0xdf, 0x6a, 0x37, 0xfa, 0xad, 0x86, 0xbf, 0xdd, 0x69,
+	0xb0, 0xa8, 0x97, 0x41, 0x26, 0xa3, 0x9e, 0x7a, 0x54, 0x53, 0xe5, 0x8e, 0x17, 0x60, 0xb6, 0xe7,
+	0x87, 0x34, 0x88, 0xf8, 0xc9, 0xaa, 0xa6, 0xa0, 0xd8, 0x2d, 0xf5, 0x89, 0x63, 0xb7, 0x49, 0x14,
+	0xdf, 0x42, 0xd5, 0x4c, 0x68, 0xe3, 0xe3, 0x2c, 0xfa, 0x07, 0x7e, 0xfb, 0xf3, 0x42, 0xaf, 0xa2,
+	0xac, 0x0c, 0xa1, 0xfc, 0x20, 0x8b, 0xf2, 0x35, 0xea, 0xd0, 0x14, 0x65, 0x9e, 0x61, 0x6a, 0x30,
+	0x67, 0x91, 0xd0, 0x22, 0x6d, 0xc9, 0x4b, 0x92, 0x2c, 0x2c, 0xf8, 0x81, 0xe7, 0x93, 0x0e, 0xe7,
+	0x74, 0xcf, 0x73, 0x6c, 0x6b, 0x20, 0x6c, 0x73, 0xf4, 0x87, 0x11, 0x23, 0x9e, 0xce, 0x31, 0xe2,
+	0x13, 0x50, 0xdf, 0x18, 0xb8, 0xd6, 0xeb, 0x3e, 0x4f, 0x99, 0xcc, 0xc5, 0xec, 0x88, 0x76, 0x43,
+	0x0d, 0xf1, 0xb8, 0x1f, 0x13, 0xc6, 0x87, 0x33, 0xb0, 0xa0, 0x9c, 0x80, 0x6d, 0x28, 0xc2, 0x5f,
+	0xe4, 0xf4, 0x0b, 0x30, 0xdb, 0x0e, 0x06, 0x66, 0xcf, 0x15, 0x97, 0x29, 0x28, 0x26, 0xd8, 0x0f,
+	0x7a, 0x6e, 0x0c, 0xb2, 0x6a, 0xc6, 0x04, 0xde, 0x84, 0x6a, 0x18, 0xb1, 0x24, 0xd9, 0x19, 0xf0,
+	0x70, 0x54, 0x6f, 0x7d, 0x73, 0x77, 0x17, 0xc8, 0xa0, 0x6f, 0x08, 0x8e, 0x66, 0xc2, 0x1b, 0x3f,
+	0x82, 0x9a, 0x8c, 0x84, 0xa1, 0x36, 0xb7, 0x34, 0xb5, 0x5c, 0x6f, 0x6d, 0xec, 0x5e, 0xd0, 0xeb,
+	0x3e, 0x4b, 0xf0, 0x4a, 0xd4, 0x37, 0x53, 0x29, 0x78, 0x11, 0x6a, 0x5d, 0xe1, 0xeb, 0xa1, 0x56,
+	0xe5, 0xda, 0x4e, 0x27, 0xf0, 0x77, 0x61, 0xc6, 0x76, 0x37, 0xbd, 0x50, 0xab, 0x71, 0x30, 0x57,
+	0x77, 0x07, 0xe6, 0xb6, 0xbb, 0xe9, 0x99, 0x31, 0x43, 0xfc, 0x08, 0xf6, 0x05, 0x34, 0x0a, 0x06,
+	0x52, 0x0b, 0x1a, 0x70, 0xbd, 0x7e, 0x6b, 0x77, 0x12, 0x4c, 0x95, 0xa5, 0x99, 0x95, 0x80, 0xd7,
+	0xa0, 0x1e, 0xa6, 0x36, 0xa6, 0xd5, 0xb9, 0x40, 0x2d, 0xc3, 0x48, 0xb1, 0x41, 0x53, 0x5d, 0x3c,
+	0x62, 0xc3, 0xf3, 0x39, 0x36, 0xfc, 0x4f, 0x04, 0x8b, 0x23, 0x61, 0x60, 0xc3, 0xa7, 0x85, 0x46,
+	0x4a, 0x60, 0x3a, 0xf4, 0xa9, 0xc5, 0x23, 0x7f, 0xbd, 0x75, 0x67, 0xcf, 0xe2, 0x02, 0x97, 0xcb,
+	0x59, 0x17, 0x85, 0xae, 0x52, 0xbe, 0xf9, 0x13, 0x04, 0x5f, 0x54, 0x38, 0xdf, 0x23, 0x91, 0xb5,
+	0x55, 0x74, 0x24, 0xe6, 0x43, 0x6c, 0x8d, 0xc8, 0x66, 0x31, 0xc1, 0x0c, 0x8d, 0x0f, 0xee, 0x0f,
+	0x7c, 0x06, 0x83, 0xfd, 0x92, 0x4e, 0x94, 0x4a, 0xfa, 0xef, 0x22, 0xd0, 0xd5, 0xc8, 0xe7, 0x39,
+	0xce, 0x9b, 0xc4, 0xda, 0x2e, 0x82, 0xb2, 0x1f, 0x2a, 0x76, 0x9b, 0xe3, 0x98, 0x32, 0x2b, 0x76,
+	0x7b, 0x87, 0x6e, 0x3f, 0x0c, 0x6a, 0x36, 0x07, 0xd4, 0xa7, 0x43, 0xa0, 0xa4, 0x8b, 0x15, 0x80,
+	0x5a, 0x84, 0x9a, 0x3b, 0x54, 0x4c, 0xa5, 0x13, 0x39, 0x45, 0x54, 0x65, 0xa4, 0x88, 0xd2, 0x60,
+	0xae, 0x9f, 0x54, 0xbd, 0xec, 0x67, 0x49, 0xb2, 0x83, 0x74, 0x02, 0xaf, 0xe7, 0x0b, 0x05, 0xc6,
+	0x04, 0x43, 0xb1, 0x6d, 0xbb, 0x6d, 0x6d, 0x36, 0x46, 0xc1, 0xc6, 0xa5, 0xea, 0xdc, 0xf7, 0x2a,
+	0xf0, 0xa5, 0x9c, 0xc3, 0x4d, 0xb4, 0x80, 0xe7, 0xe3, 0x84, 0x89, 0x1d, 0xce, 0x8d, 0xb5, 0xc3,
+	0xea, 0x24, 0x3b, 0xac, 0xe5, 0x68, 0xe5, 0x9d, 0x0a, 0x2c, 0xe5, 0x68, 0x65, 0x72, 0x42, 0x7d,
+	0x6e, 0xd4, 0xb2, 0xe9, 0x05, 0xe2, 0xc6, 0xab, 0x66, 0x4c, 0x30, 0xcf, 0xf0, 0x02, 0x7f, 0x8b,
+	0xb8, 0x5a, 0x35, 0xf6, 0x8c, 0x98, 0x2a, 0xa5, 0x90, 0xff, 0x22, 0xd0, 0xa4, 0x16, 0xae, 0x58,
+	0x5c, 0x27, 0x3d, 0xf7, 0xf9, 0x57, 0xc4, 0x02, 0xcc, 0x12, 0x8e, 0x56, 0x18, 0x88, 0xa0, 0x46,
+	0x8e, 0x5c, 0xcd, 0x8f, 0x89, 0x47, 0xb2, 0x47, 0x0e, 0xd7, 0xed, 0x30, 0x92, 0x05, 0x2d, 0xde,
+	0x84, 0xb9, 0x98, 0x5b, 0x5c, 0xc2, 0xd4, 0x5b, 0xeb, 0xbb, 0x4d, 0x6c, 0x19, 0xf5, 0x4a, 0xe6,
+	0xc6, 0xcb, 0x70, 0x24, 0x37, 0xfa, 0x08, 0x18, 0x3a, 0x54, 0x65, 0x32, 0x17, 0x17, 0x90, 0xd0,
+	0xc6, 0x7f, 0xa6, 0xb2, 0x61, 0xdd, 0x6b, 0xaf, 0x7b, 0x9d, 0x82, 0xb7, 0x60, 0xf1, 0xa5, 0x69,
+	0x30, 0xe7, 0x7b, 0x6d, 0xe5, 0xd9, 0x27, 0x49, 0xb6, 0xcf, 0xf2, 0xdc, 0x88, 0xd8, 0x2e, 0x0d,
+	0x44, 0x7e, 0x49, 0x27, 0x98, 0xb2, 0x43, 0xdb, 0xb5, 0xe8, 0x06, 0xb5, 0x3c, 0xb7, 0x1d, 0xf2,
+	0x5b, 0x9b, 0x32, 0x33, 0x73, 0xf8, 0x16, 0xd4, 0x38, 0x7d, 0xdf, 0xee, 0xc6, 0x41, 0xb8, 0xde,
+	0x5a, 0x69, 0xc4, 0xad, 0x92, 0x86, 0xda, 0x2a, 0x49, 0x75, 0xd8, 0xa5, 0x11, 0x69, 0xf4, 0x2f,
+	0x36, 0xd8, 0x0e, 0x33, 0xdd, 0xcc, 0xb0, 0x44, 0xc4, 0x76, 0xd6, 0x6d, 0x97, 0x17, 0x58, 0x4c,
+	0x54, 0x3a, 0xc1, 0x0c, 0x62, 0xd3, 0x73, 0x1c, 0xef, 0xb1, 0xf4, 0x81, 0x98, 0x62, 0xbb, 0x7a,
+	0x6e, 0x64, 0x3b, 0x5c, 0x7e, 0xec, 0x00, 0xe9, 0x04, 0xdf, 0x65, 0x3b, 0x11, 0x0d, 0x78, 0x09,
+	0x53, 0x33, 0x05, 0x95, 0x98, 0x5c, 0x3d, 0xee, 0x0b, 0x48, 0xdf, 0x8b, 0x8d, 0x73, 0x5e, 0x35,
+	0xce, 0x61, 0x83, 0xdf, 0x97, 0xf3, 0x6e, 0xe6, 0xcd, 0x10, 0xda, 0xb7, 0xbd, 0x5e, 0xa8, 0xed,
+	0x8f, 0x93, 0xb8, 0xa4, 0x47, 0x0c, 0xf6, 0x40, 0x8e, 0xc1, 0xfe, 0x1e, 0x41, 0x75, 0xdd, 0xeb,
+	0x5c, 0x77, 0xa3, 0x60, 0xc0, 0x2b, 0x7b, 0xcf, 0x8d, 0xa8, 0x2b, 0xad, 0x42, 0x92, 0x4c, 0xd5,
+	0x91, 0xdd, 0xa5, 0x1b, 0x11, 0xe9, 0xfa, 0xa2, 0x26, 0xd9, 0x91, 0xaa, 0x93, 0xcd, 0xec, 0xf8,
+	0x0e, 0x09, 0x23, 0xee, 0xbd, 0x55, 0x93, 0x8f, 0x19, 0xd0, 0x64, 0xc1, 0x46, 0x14, 0x08, 0xd7,
+	0xcd, 0xcc, 0xa9, 0x86, 0x34, 0x13, 0x63, 0x13, 0xa4, 0xb1, 0x01, 0x2f, 0x25, 0xa5, 0xec, 0x7d,
+	0x1a, 0x74, 0x6d, 0x97, 0x14, 0xc7, 0xdb, 0x32, 0x5d, 0x98, 0x07, 0x19, 0x07, 0x62, 0xf5, 0xdf,
+	0x43, 0xdb, 0x6d, 0x7b, 0x8f, 0x0b, 0x1c, 0xa1, 0x0c, 0xdb, 0xbf, 0x65, 0xfb, 0x2d, 0x0a, 0xdf,
+	0xc4, 0x37, 0x6f, 0xc1, 0x3e, 0xe6, 0xc5, 0x7d, 0x2a, 0x7e, 0x10, 0x81, 0xc2, 0x18, 0xf7, 0x24,
+	0x4f, 0x79, 0x98, 0xd9, 0x8d, 0x78, 0x1d, 0x0e, 0x90, 0x30, 0xb4, 0x3b, 0x2e, 0x6d, 0x4b, 0x5e,
+	0x95, 0xd2, 0xbc, 0x86, 0xb7, 0xc6, 0xcf, 0x3e, 0xbe, 0x42, 0xdc, 0x9d, 0x24, 0x8d, 0x1f, 0x23,
+	0x38, 0x9c, 0xcb, 0x24, 0xb1, 0x75, 0xa4, 0x84, 0x57, 0x1d, 0xaa, 0xa1, 0xb5, 0x45, 0xdb, 0x3d,
+	0x87, 0xca, 0xbe, 0x86, 0xa4, 0xd9, 0x6f, 0xed, 0x5e, 0x7c, 0x93, 0x22, 0xbc, 0x27, 0x34, 0x3e,
+	0x06, 0xd0, 0x25, 0x6e, 0x8f, 0x38, 0x1c, 0xc2, 0x34, 0x87, 0xa0, 0xcc, 0x18, 0x8b, 0xa0, 0xe7,
+	0x99, 0x81, 0xe8, 0x24, 0xfc, 0x03, 0xc1, 0x7e, 0x19, 0x06, 0xc5, 0x1d, 0x2e, 0xc3, 0x01, 0x45,
+	0x0d, 0x77, 0xd3, 0xeb, 0x1c, 0x9e, 0x9e, 0x10, 0xe2, 0xa4, 0x2d, 0x4c, 0x65, 0xbb, 0x97, 0xfd,
+	0x4c, 0xff, 0xb1, 0x74, 0x1e, 0x42, 0x3b, 0xaa, 0xc4, 0x7e, 0x04, 0xda, 0x1d, 0xe2, 0x92, 0x0e,
+	0x6d, 0x27, 0x87, 0x4b, 0x0c, 0xe9, 0x07, 0xea, 0x63, 0x79, 0xd7, 0x4f, 0xd3, 0xa4, 0x9c, 0xb1,
+	0x37, 0x37, 0xe5, 0xc3, 0x3b, 0x80, 0xea, 0xba, 0xed, 0x6e, 0xb3, 0xf7, 0x1b, 0x3b, 0x57, 0x64,
+	0x47, 0x8e, 0xd4, 0x61, 0x4c, 0xe0, 0x83, 0x30, 0xd5, 0x0b, 0x1c, 0x71, 0xcf, 0x6c, 0x88, 0x97,
+	0xa0, 0xde, 0xa6, 0xa1, 0x15, 0xd8, 0xbe, 0xb8, 0x65, 0xde, 0xe8, 0x53, 0xa6, 0x98, 0xb6, 0x6d,
+	0xcb, 0x73, 0xaf, 0x39, 0x24, 0x0c, 0x65, 0x62, 0x48, 0x26, 0x8c, 0x57, 0x61, 0x1f, 0x93, 0x99,
+	0x1e, 0xf3, 0x5c, 0xf6, 0x98, 0x87, 0x33, 0xf0, 0x25, 0x3c, 0x89, 0xf8, 0x26, 0xbc, 0xc0, 0xf2,
+	0xf1, 0x15, 0xdf, 0x17, 0x4c, 0x4a, 0x16, 0x23, 0x53, 0x43, 0x97, 0xde, 0xfa, 0xa9, 0x01, 0x58,
+	0xb5, 0x79, 0x1a, 0xf4, 0x6d, 0x8b, 0xe2, 0x77, 0x11, 0x4c, 0x33, 0x01, 0xf8, 0xe8, 0x38, 0x17,
+	0xe3, 0xb6, 0xa7, 0xef, 0xdd, 0x83, 0x8e, 0x49, 0x33, 0x16, 0xdf, 0xfa, 0xfb, 0xbf, 0xdf, 0xab,
+	0x2c, 0xe0, 0x17, 0xf9, 0x67, 0x84, 0xfe, 0x45, 0xb5, 0xa5, 0x1f, 0xe2, 0xb7, 0x11, 0x60, 0x51,
+	0x85, 0x28, 0xdd, 0x5d, 0x7c, 0x6e, 0x1c, 0xc4, 0x9c, 0x2e, 0xb0, 0x7e, 0x54, 0x89, 0xf6, 0x0d,
+	0xcb, 0x0b, 0x28, 0x8b, 0xed, 0x7c, 0x01, 0x07, 0xb0, 0xc2, 0x01, 0x9c, 0xc4, 0x46, 0x1e, 0x80,
+	0xe6, 0x13, 0xa6, 0xb7, 0xa7, 0x4d, 0x1a, 0xcb, 0xfd, 0x08, 0xc1, 0xcc, 0x43, 0x5e, 0x73, 0x4f,
+	0x50, 0xd2, 0xc6, 0x9e, 0x29, 0x89, 0x8b, 0xe3, 0x68, 0x8d, 0x13, 0x1c, 0xe9, 0x51, 0x7c, 0x44,
+	0x22, 0x0d, 0xa3, 0x80, 0x92, 0x6e, 0x06, 0xf0, 0x05, 0x84, 0x3f, 0x41, 0x30, 0x1b, 0xb7, 0x1b,
+	0xf1, 0xa9, 0x71, 0x28, 0x33, 0xed, 0x48, 0x7d, 0xef, 0x7a, 0x77, 0xc6, 0x59, 0x8e, 0xf1, 0x84,
+	0x91, 0x7b, 0x9d, 0x6b, 0x99, 0xce, 0xde, 0xfb, 0x08, 0xa6, 0x6e, 0xd2, 0x89, 0xf6, 0xb6, 0x87,
+	0xe0, 0x46, 0x14, 0x98, 0x73, 0xd5, 0xf8, 0x63, 0x04, 0x2f, 0xdd, 0xa4, 0x51, 0x7e, 0xaa, 0xc3,
+	0xcb, 0x93, 0xf3, 0x8f, 0x30, 0xbb, 0x73, 0x25, 0x56, 0x26, 0x31, 0xbe, 0xc9, 0x91, 0x9d, 0xc5,
+	0x67, 0x8a, 0x8c, 0x30, 0x1c, 0xb8, 0xd6, 0x63, 0x81, 0xe3, 0xcf, 0x08, 0x0e, 0x0e, 0x7f, 0x6b,
+	0xc1, 0xd9, 0xe4, 0x98, 0xfb, 0x29, 0x46, 0xbf, 0xbb, 0xdb, 0x58, 0x9a, 0x65, 0x6a, 0x5c, 0xe1,
+	0xc8, 0x5f, 0xc1, 0x2f, 0x17, 0x21, 0x97, 0x4d, 0xca, 0xb0, 0xf9, 0x44, 0x0e, 0x9f, 0xf2, 0x8f,
+	0x7f, 0x1c, 0xf6, 0x5b, 0x08, 0xe6, 0x6f, 0xd2, 0xe8, 0x4e, 0xd2, 0xa3, 0x3b, 0x55, 0xaa, 0x87,
+	0xaf, 0x2f, 0x36, 0x94, 0x6f, 0x74, 0xf2, 0xa7, 0x44, 0xa5, 0xab, 0x1c, 0xd8, 0x19, 0x7c, 0xaa,
+	0x08, 0x58, 0xda, 0x17, 0xfc, 0x08, 0xc1, 0x61, 0x15, 0x44, 0xfa, 0x85, 0xe3, 0x2b, 0x3b, 0xfb,
+	0xa2, 0x20, 0xbe, 0x4b, 0x4c, 0x40, 0xd7, 0xe2, 0xe8, 0xce, 0x1b, 0xf9, 0x17, 0xde, 0x1d, 0x41,
+	0xb1, 0x86, 0x56, 0x96, 0x11, 0xfe, 0x03, 0x82, 0xd9, 0xb8, 0x09, 0x37, 0x5e, 0x47, 0x99, 0x5e,
+	0xfd, 0x5e, 0x7a, 0xcf, 0x75, 0x0e, 0xf9, 0xeb, 0xfa, 0x85, 0x7c, 0x85, 0xaa, 0xfb, 0xe5, 0xd5,
+	0x36, 0xb8, 0x96, 0xb3, 0x6e, 0xff, 0x1b, 0x04, 0x90, 0x36, 0x12, 0xf1, 0xd9, 0xe2, 0x73, 0x28,
+	0xcd, 0x46, 0x7d, 0x6f, 0x5b, 0x89, 0x46, 0x83, 0x9f, 0x67, 0x59, 0x5f, 0x2a, 0xf4, 0x39, 0x9f,
+	0x5a, 0x6b, 0x71, 0xd3, 0xf1, 0x97, 0x08, 0x66, 0x78, 0x9f, 0x08, 0x9f, 0x1c, 0x87, 0x59, 0x6d,
+	0x23, 0xed, 0xa5, 0xea, 0x4f, 0x73, 0xa8, 0x4b, 0xad, 0xa2, 0xc0, 0xb5, 0x86, 0x56, 0x70, 0x1f,
+	0x66, 0xe3, 0x9e, 0xcd, 0x78, 0xf3, 0xc8, 0xf4, 0x74, 0xf4, 0xa5, 0x82, 0x44, 0x1a, 0x1b, 0xaa,
+	0x88, 0x99, 0x2b, 0x93, 0x62, 0xe6, 0x34, 0x0b, 0x6b, 0xf8, 0x44, 0x51, 0xd0, 0xfb, 0x0c, 0x14,
+	0x73, 0x8e, 0xa3, 0x3b, 0x65, 0x2c, 0x4d, 0x8a, 0x9b, 0x4c, 0x3b, 0x3f, 0x47, 0x70, 0x70, 0xb8,
+	0xe4, 0xc4, 0x47, 0x86, 0x62, 0xa6, 0x5a, 0x67, 0xeb, 0x59, 0x2d, 0x8e, 0x2b, 0x57, 0x8d, 0x6f,
+	0x70, 0x14, 0x6b, 0xf8, 0xf2, 0x44, 0xcf, 0xb8, 0x2b, 0xa3, 0x0e, 0x63, 0xb4, 0x9a, 0x7e, 0xb3,
+	0xf8, 0x2d, 0x82, 0x79, 0xc9, 0xf7, 0x7e, 0x40, 0x69, 0x31, 0xac, 0xbd, 0x73, 0x04, 0x26, 0xcb,
+	0x78, 0x95, 0xc3, 0xff, 0x2a, 0xbe, 0x54, 0x12, 0xbe, 0x84, 0xbd, 0x1a, 0x31, 0xa4, 0x7f, 0x44,
+	0x70, 0xe8, 0x61, 0x6c, 0xf7, 0x9f, 0x13, 0xfe, 0x6b, 0x1c, 0xff, 0xd7, 0xf0, 0x2b, 0x05, 0x75,
+	0xd1, 0xa4, 0x63, 0x5c, 0x40, 0xf8, 0xd7, 0x08, 0xaa, 0xb2, 0x03, 0x8f, 0xcf, 0x8c, 0x75, 0x8c,
+	0x6c, 0x8f, 0x7e, 0x2f, 0x8d, 0x59, 0x14, 0x01, 0xc6, 0xc9, 0xc2, 0x54, 0x2a, 0xe4, 0x33, 0x83,
+	0x7e, 0x1f, 0x01, 0x4e, 0xde, 0x8b, 0xc9, 0x0b, 0x12, 0x9f, 0xce, 0x88, 0x1a, 0xdb, 0x60, 0xd0,
+	0xcf, 0x4c, 0x5c, 0x97, 0x4d, 0xa5, 0x2b, 0x85, 0xa9, 0xd4, 0x4b, 0xe4, 0xbf, 0x83, 0xa0, 0x7e,
+	0x93, 0x26, 0x35, 0x7b, 0x81, 0x2e, 0xb3, 0x9f, 0x16, 0xf4, 0xe5, 0xc9, 0x0b, 0x05, 0xa2, 0xf3,
+	0x1c, 0xd1, 0x69, 0x5c, 0xac, 0x2a, 0x09, 0xe0, 0x43, 0x04, 0xfb, 0xee, 0xa9, 0x26, 0x8a, 0xcf,
+	0x4f, 0x92, 0x94, 0x89, 0xe4, 0xe5, 0x71, 0x7d, 0x99, 0xe3, 0x5a, 0x35, 0x4a, 0xe1, 0x5a, 0x13,
+	0xfd, 0xfb, 0x5f, 0xa0, 0xf8, 0x69, 0x37, 0xd4, 0x7d, 0xfd, 0x7f, 0xf5, 0x56, 0xd0, 0xc4, 0x35,
+	0x2e, 0x71, 0x7c, 0x0d, 0x7c, 0xbe, 0x0c, 0xbe, 0xa6, 0x68, 0xc9, 0xe2, 0x0f, 0x10, 0x1c, 0xe2,
+	0xfd, 0x6f, 0x95, 0xf1, 0x50, 0x8a, 0x19, 0xd7, 0x2d, 0x2f, 0x91, 0x62, 0x44, 0xfc, 0x31, 0x76,
+	0x04, 0x6a, 0x4d, 0xf6, 0xb6, 0x7f, 0x86, 0x60, 0xbf, 0x4c, 0x6a, 0xe2, 0x76, 0x57, 0x27, 0x29,
+	0x6e, 0xa7, 0x49, 0x50, 0x98, 0xdb, 0x4a, 0x39, 0x73, 0xfb, 0x04, 0xc1, 0x9c, 0xe8, 0x3d, 0x17,
+	0x94, 0x0a, 0x4a, 0x73, 0x5a, 0x1f, 0x7a, 0xf9, 0x8b, 0xa6, 0xa6, 0xf1, 0x3d, 0x2e, 0xf6, 0x01,
+	0x6e, 0x16, 0x89, 0xf5, 0xbd, 0x76, 0xd8, 0x7c, 0x22, 0x3a, 0x8a, 0x4f, 0x9b, 0x8e, 0xd7, 0x09,
+	0xdf, 0x30, 0x70, 0x61, 0x42, 0x64, 0x6b, 0x2e, 0x20, 0x1c, 0x41, 0x8d, 0x19, 0x07, 0x6f, 0x27,
+	0xe0, 0xa5, 0xa1, 0xe6, 0xc3, 0x48, 0xa7, 0x41, 0xd7, 0x47, 0xda, 0x13, 0x69, 0x06, 0x14, 0xcf,
+	0x3e, 0x7c, 0xbc, 0x50, 0x2c, 0x17, 0xf4, 0x36, 0x82, 0x43, 0xaa, 0xb5, 0xc7, 0xe2, 0x4b, 0xdb,
+	0x7a, 0x11, 0x0a, 0x51, 0x54, 0xe3, 0x95, 0x52, 0x86, 0xc4, 0xe1, 0x5c, 0xbd, 0xf1, 0xa7, 0x67,
+	0xc7, 0xd0, 0x5f, 0x9f, 0x1d, 0x43, 0xff, 0x7a, 0x76, 0x0c, 0xbd, 0x71, 0xb9, 0xdc, 0x1f, 0x0f,
+	0x2d, 0xc7, 0xa6, 0x6e, 0xa4, 0xb2, 0xff, 0x5f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x83, 0x0c, 0x3f,
+	0x61, 0x5e, 0x29, 0x00, 0x00,
 }
 
 // Reference imports to suppress errors if they are not otherwise used.
@@ -2669,6 +2854,10 @@ type ApplicationServiceClient interface {
 	DeleteResource(ctx context.Context, in *ApplicationResourceDeleteRequest, opts ...grpc.CallOption) (*ApplicationResponse, error)
 	// PodLogs returns stream of log entries for the specified pod. Pod
 	PodLogs(ctx context.Context, in *ApplicationPodLogsQuery, opts ...grpc.CallOption) (ApplicationService_PodLogsClient, error)
+	// ListLinks returns the list of all application deep links
+	ListLinks(ctx context.Context, in *ListAppLinksRequest, opts ...grpc.CallOption) (*LinksResponse, error)
+	// ListResourceLinks returns the list of all resource deep links
+	ListResourceLinks(ctx context.Context, in *ApplicationResourceRequest, opts ...grpc.CallOption) (*LinksResponse, error)
 }
 
 type applicationServiceClient struct {
@@ -2998,6 +3187,24 @@ func (x *applicationServicePodLogsClient) Recv() (*LogEntry, error) {
 	return m, nil
 }
 
+func (c *applicationServiceClient) ListLinks(ctx context.Context, in *ListAppLinksRequest, opts ...grpc.CallOption) (*LinksResponse, error) {
+	out := new(LinksResponse)
+	err := c.cc.Invoke(ctx, "/application.ApplicationService/ListLinks", in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+func (c *applicationServiceClient) ListResourceLinks(ctx context.Context, in *ApplicationResourceRequest, opts ...grpc.CallOption) (*LinksResponse, error) {
+	out := new(LinksResponse)
+	err := c.cc.Invoke(ctx, "/application.ApplicationService/ListResourceLinks", in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
 // ApplicationServiceServer is the server API for ApplicationService service.
 type ApplicationServiceServer interface {
 	// List returns list of applications
@@ -3050,6 +3257,10 @@ type ApplicationServiceServer interface {
 	DeleteResource(context.Context, *ApplicationResourceDeleteRequest) (*ApplicationResponse, error)
 	// PodLogs returns stream of log entries for the specified pod. Pod
 	PodLogs(*ApplicationPodLogsQuery, ApplicationService_PodLogsServer) error
+	// ListLinks returns the list of all application deep links
+	ListLinks(context.Context, *ListAppLinksRequest) (*LinksResponse, error)
+	// ListResourceLinks returns the list of all resource deep links
+	ListResourceLinks(context.Context, *ApplicationResourceRequest) (*LinksResponse, error)
 }
 
 // UnimplementedApplicationServiceServer can be embedded to have forward compatible implementations.
@@ -3131,6 +3342,12 @@ func (*UnimplementedApplicationServiceServer) DeleteResource(ctx context.Context
 func (*UnimplementedApplicationServiceServer) PodLogs(req *ApplicationPodLogsQuery, srv ApplicationService_PodLogsServer) error {
 	return status.Errorf(codes.Unimplemented, "method PodLogs not implemented")
 }
+func (*UnimplementedApplicationServiceServer) ListLinks(ctx context.Context, req *ListAppLinksRequest) (*LinksResponse, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method ListLinks not implemented")
+}
+func (*UnimplementedApplicationServiceServer) ListResourceLinks(ctx context.Context, req *ApplicationResourceRequest) (*LinksResponse, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method ListResourceLinks not implemented")
+}
 
 func RegisterApplicationServiceServer(s *grpc.Server, srv ApplicationServiceServer) {
 	s.RegisterService(&_ApplicationService_serviceDesc, srv)
@@ -3603,6 +3820,42 @@ func (x *applicationServicePodLogsServer) Send(m *LogEntry) error {
 	return x.ServerStream.SendMsg(m)
 }
 
+func _ApplicationService_ListLinks_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(ListAppLinksRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(ApplicationServiceServer).ListLinks(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/application.ApplicationService/ListLinks",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(ApplicationServiceServer).ListLinks(ctx, req.(*ListAppLinksRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+func _ApplicationService_ListResourceLinks_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(ApplicationResourceRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(ApplicationServiceServer).ListResourceLinks(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/application.ApplicationService/ListResourceLinks",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(ApplicationServiceServer).ListResourceLinks(ctx, req.(*ApplicationResourceRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
 var _ApplicationService_serviceDesc = grpc.ServiceDesc{
 	ServiceName: "application.ApplicationService",
 	HandlerType: (*ApplicationServiceServer)(nil),
@@ -3691,6 +3944,14 @@ var _ApplicationService_serviceDesc = grpc.ServiceDesc{
 			MethodName: "DeleteResource",
 			Handler:    _ApplicationService_DeleteResource_Handler,
 		},
+		{
+			MethodName: "ListLinks",
+			Handler:    _ApplicationService_ListLinks_Handler,
+		},
+		{
+			MethodName: "ListResourceLinks",
+			Handler:    _ApplicationService_ListResourceLinks_Handler,
+		},
 	},
 	Streams: []grpc.StreamDesc{
 		{
@@ -5790,90 +6051,233 @@ func (m *ManagedResourcesResponse) MarshalToSizedBuffer(dAtA []byte) (int, error
 	return len(dAtA) - i, nil
 }
 
-func encodeVarintApplication(dAtA []byte, offset int, v uint64) int {
-	offset -= sovApplication(v)
-	base := offset
-	for v >= 1<<7 {
-		dAtA[offset] = uint8(v&0x7f | 0x80)
-		v >>= 7
-		offset++
+func (m *LinkInfo) Marshal() (dAtA []byte, err error) {
+	size := m.Size()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalToSizedBuffer(dAtA[:size])
+	if err != nil {
+		return nil, err
 	}
-	dAtA[offset] = uint8(v)
-	return base
+	return dAtA[:n], nil
 }
-func (m *ApplicationQuery) Size() (n int) {
-	if m == nil {
-		return 0
-	}
+
+func (m *LinkInfo) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *LinkInfo) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
 	var l int
 	_ = l
-	if m.Name != nil {
-		l = len(*m.Name)
-		n += 1 + l + sovApplication(uint64(l))
-	}
-	if m.Refresh != nil {
-		l = len(*m.Refresh)
-		n += 1 + l + sovApplication(uint64(l))
-	}
-	if len(m.Projects) > 0 {
-		for _, s := range m.Projects {
-			l = len(s)
-			n += 1 + l + sovApplication(uint64(l))
-		}
-	}
-	if m.ResourceVersion != nil {
-		l = len(*m.ResourceVersion)
-		n += 1 + l + sovApplication(uint64(l))
+	if m.XXX_unrecognized != nil {
+		i -= len(m.XXX_unrecognized)
+		copy(dAtA[i:], m.XXX_unrecognized)
 	}
-	if m.Selector != nil {
-		l = len(*m.Selector)
-		n += 1 + l + sovApplication(uint64(l))
+	if m.IconClass != nil {
+		i -= len(*m.IconClass)
+		copy(dAtA[i:], *m.IconClass)
+		i = encodeVarintApplication(dAtA, i, uint64(len(*m.IconClass)))
+		i--
+		dAtA[i] = 0x22
 	}
-	if m.Repo != nil {
-		l = len(*m.Repo)
-		n += 1 + l + sovApplication(uint64(l))
+	if m.Description != nil {
+		i -= len(*m.Description)
+		copy(dAtA[i:], *m.Description)
+		i = encodeVarintApplication(dAtA, i, uint64(len(*m.Description)))
+		i--
+		dAtA[i] = 0x1a
 	}
-	if m.AppNamespace != nil {
-		l = len(*m.AppNamespace)
-		n += 1 + l + sovApplication(uint64(l))
+	if m.Url == nil {
+		return 0, github_com_gogo_protobuf_proto.NewRequiredNotSetError("url")
+	} else {
+		i -= len(*m.Url)
+		copy(dAtA[i:], *m.Url)
+		i = encodeVarintApplication(dAtA, i, uint64(len(*m.Url)))
+		i--
+		dAtA[i] = 0x12
 	}
-	if m.XXX_unrecognized != nil {
-		n += len(m.XXX_unrecognized)
+	if m.Title == nil {
+		return 0, github_com_gogo_protobuf_proto.NewRequiredNotSetError("title")
+	} else {
+		i -= len(*m.Title)
+		copy(dAtA[i:], *m.Title)
+		i = encodeVarintApplication(dAtA, i, uint64(len(*m.Title)))
+		i--
+		dAtA[i] = 0xa
 	}
-	return n
+	return len(dAtA) - i, nil
 }
 
-func (m *NodeQuery) Size() (n int) {
-	if m == nil {
-		return 0
-	}
-	var l int
-	_ = l
-	if m.Name != nil {
-		l = len(*m.Name)
-		n += 1 + l + sovApplication(uint64(l))
-	}
-	if m.AppNamespace != nil {
-		l = len(*m.AppNamespace)
-		n += 1 + l + sovApplication(uint64(l))
-	}
-	if m.XXX_unrecognized != nil {
-		n += len(m.XXX_unrecognized)
+func (m *LinksResponse) Marshal() (dAtA []byte, err error) {
+	size := m.Size()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalToSizedBuffer(dAtA[:size])
+	if err != nil {
+		return nil, err
 	}
-	return n
+	return dAtA[:n], nil
 }
 
-func (m *RevisionMetadataQuery) Size() (n int) {
-	if m == nil {
-		return 0
-	}
+func (m *LinksResponse) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *LinksResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
 	var l int
 	_ = l
-	if m.Name != nil {
-		l = len(*m.Name)
-		n += 1 + l + sovApplication(uint64(l))
-	}
-	if m.Revision != nil {
+	if m.XXX_unrecognized != nil {
+		i -= len(m.XXX_unrecognized)
+		copy(dAtA[i:], m.XXX_unrecognized)
+	}
+	if len(m.Items) > 0 {
+		for iNdEx := len(m.Items) - 1; iNdEx >= 0; iNdEx-- {
+			{
+				size, err := m.Items[iNdEx].MarshalToSizedBuffer(dAtA[:i])
+				if err != nil {
+					return 0, err
+				}
+				i -= size
+				i = encodeVarintApplication(dAtA, i, uint64(size))
+			}
+			i--
+			dAtA[i] = 0xa
+		}
+	}
+	return len(dAtA) - i, nil
+}
+
+func (m *ListAppLinksRequest) Marshal() (dAtA []byte, err error) {
+	size := m.Size()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalToSizedBuffer(dAtA[:size])
+	if err != nil {
+		return nil, err
+	}
+	return dAtA[:n], nil
+}
+
+func (m *ListAppLinksRequest) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *ListAppLinksRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	if m.XXX_unrecognized != nil {
+		i -= len(m.XXX_unrecognized)
+		copy(dAtA[i:], m.XXX_unrecognized)
+	}
+	if m.Namespace != nil {
+		i -= len(*m.Namespace)
+		copy(dAtA[i:], *m.Namespace)
+		i = encodeVarintApplication(dAtA, i, uint64(len(*m.Namespace)))
+		i--
+		dAtA[i] = 0x1a
+	}
+	if m.Name == nil {
+		return 0, github_com_gogo_protobuf_proto.NewRequiredNotSetError("name")
+	} else {
+		i -= len(*m.Name)
+		copy(dAtA[i:], *m.Name)
+		i = encodeVarintApplication(dAtA, i, uint64(len(*m.Name)))
+		i--
+		dAtA[i] = 0xa
+	}
+	return len(dAtA) - i, nil
+}
+
+func encodeVarintApplication(dAtA []byte, offset int, v uint64) int {
+	offset -= sovApplication(v)
+	base := offset
+	for v >= 1<<7 {
+		dAtA[offset] = uint8(v&0x7f | 0x80)
+		v >>= 7
+		offset++
+	}
+	dAtA[offset] = uint8(v)
+	return base
+}
+func (m *ApplicationQuery) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	if m.Name != nil {
+		l = len(*m.Name)
+		n += 1 + l + sovApplication(uint64(l))
+	}
+	if m.Refresh != nil {
+		l = len(*m.Refresh)
+		n += 1 + l + sovApplication(uint64(l))
+	}
+	if len(m.Projects) > 0 {
+		for _, s := range m.Projects {
+			l = len(s)
+			n += 1 + l + sovApplication(uint64(l))
+		}
+	}
+	if m.ResourceVersion != nil {
+		l = len(*m.ResourceVersion)
+		n += 1 + l + sovApplication(uint64(l))
+	}
+	if m.Selector != nil {
+		l = len(*m.Selector)
+		n += 1 + l + sovApplication(uint64(l))
+	}
+	if m.Repo != nil {
+		l = len(*m.Repo)
+		n += 1 + l + sovApplication(uint64(l))
+	}
+	if m.AppNamespace != nil {
+		l = len(*m.AppNamespace)
+		n += 1 + l + sovApplication(uint64(l))
+	}
+	if m.XXX_unrecognized != nil {
+		n += len(m.XXX_unrecognized)
+	}
+	return n
+}
+
+func (m *NodeQuery) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	if m.Name != nil {
+		l = len(*m.Name)
+		n += 1 + l + sovApplication(uint64(l))
+	}
+	if m.AppNamespace != nil {
+		l = len(*m.AppNamespace)
+		n += 1 + l + sovApplication(uint64(l))
+	}
+	if m.XXX_unrecognized != nil {
+		n += len(m.XXX_unrecognized)
+	}
+	return n
+}
+
+func (m *RevisionMetadataQuery) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	if m.Name != nil {
+		l = len(*m.Name)
+		n += 1 + l + sovApplication(uint64(l))
+	}
+	if m.Revision != nil {
 		l = len(*m.Revision)
 		n += 1 + l + sovApplication(uint64(l))
 	}
@@ -6739,6 +7143,72 @@ func (m *ManagedResourcesResponse) Size() (n int) {
 	return n
 }
 
+func (m *LinkInfo) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	if m.Title != nil {
+		l = len(*m.Title)
+		n += 1 + l + sovApplication(uint64(l))
+	}
+	if m.Url != nil {
+		l = len(*m.Url)
+		n += 1 + l + sovApplication(uint64(l))
+	}
+	if m.Description != nil {
+		l = len(*m.Description)
+		n += 1 + l + sovApplication(uint64(l))
+	}
+	if m.IconClass != nil {
+		l = len(*m.IconClass)
+		n += 1 + l + sovApplication(uint64(l))
+	}
+	if m.XXX_unrecognized != nil {
+		n += len(m.XXX_unrecognized)
+	}
+	return n
+}
+
+func (m *LinksResponse) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	if len(m.Items) > 0 {
+		for _, e := range m.Items {
+			l = e.Size()
+			n += 1 + l + sovApplication(uint64(l))
+		}
+	}
+	if m.XXX_unrecognized != nil {
+		n += len(m.XXX_unrecognized)
+	}
+	return n
+}
+
+func (m *ListAppLinksRequest) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	if m.Name != nil {
+		l = len(*m.Name)
+		n += 1 + l + sovApplication(uint64(l))
+	}
+	if m.Namespace != nil {
+		l = len(*m.Namespace)
+		n += 1 + l + sovApplication(uint64(l))
+	}
+	if m.XXX_unrecognized != nil {
+		n += len(m.XXX_unrecognized)
+	}
+	return n
+}
+
 func sovApplication(x uint64) (n int) {
 	return (math_bits.Len64(x|1) + 6) / 7
 }
@@ -12832,6 +13302,405 @@ func (m *ManagedResourcesResponse) Unmarshal(dAtA []byte) error {
 	}
 	return nil
 }
+func (m *LinkInfo) Unmarshal(dAtA []byte) error {
+	var hasFields [1]uint64
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowApplication
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= uint64(b&0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: LinkInfo: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: LinkInfo: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Title", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowApplication
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthApplication
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthApplication
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			s := string(dAtA[iNdEx:postIndex])
+			m.Title = &s
+			iNdEx = postIndex
+			hasFields[0] |= uint64(0x00000001)
+		case 2:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Url", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowApplication
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthApplication
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthApplication
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			s := string(dAtA[iNdEx:postIndex])
+			m.Url = &s
+			iNdEx = postIndex
+			hasFields[0] |= uint64(0x00000002)
+		case 3:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Description", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowApplication
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthApplication
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthApplication
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			s := string(dAtA[iNdEx:postIndex])
+			m.Description = &s
+			iNdEx = postIndex
+		case 4:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field IconClass", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowApplication
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthApplication
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthApplication
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			s := string(dAtA[iNdEx:postIndex])
+			m.IconClass = &s
+			iNdEx = postIndex
+		default:
+			iNdEx = preIndex
+			skippy, err := skipApplication(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if (skippy < 0) || (iNdEx+skippy) < 0 {
+				return ErrInvalidLengthApplication
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)
+			iNdEx += skippy
+		}
+	}
+	if hasFields[0]&uint64(0x00000001) == 0 {
+		return github_com_gogo_protobuf_proto.NewRequiredNotSetError("title")
+	}
+	if hasFields[0]&uint64(0x00000002) == 0 {
+		return github_com_gogo_protobuf_proto.NewRequiredNotSetError("url")
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
+func (m *LinksResponse) Unmarshal(dAtA []byte) error {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowApplication
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= uint64(b&0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: LinksResponse: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: LinksResponse: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Items", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowApplication
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthApplication
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthApplication
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.Items = append(m.Items, &LinkInfo{})
+			if err := m.Items[len(m.Items)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		default:
+			iNdEx = preIndex
+			skippy, err := skipApplication(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if (skippy < 0) || (iNdEx+skippy) < 0 {
+				return ErrInvalidLengthApplication
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)
+			iNdEx += skippy
+		}
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
+func (m *ListAppLinksRequest) Unmarshal(dAtA []byte) error {
+	var hasFields [1]uint64
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowApplication
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= uint64(b&0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: ListAppLinksRequest: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: ListAppLinksRequest: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowApplication
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthApplication
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthApplication
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			s := string(dAtA[iNdEx:postIndex])
+			m.Name = &s
+			iNdEx = postIndex
+			hasFields[0] |= uint64(0x00000001)
+		case 3:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Namespace", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowApplication
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthApplication
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthApplication
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			s := string(dAtA[iNdEx:postIndex])
+			m.Namespace = &s
+			iNdEx = postIndex
+		default:
+			iNdEx = preIndex
+			skippy, err := skipApplication(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if (skippy < 0) || (iNdEx+skippy) < 0 {
+				return ErrInvalidLengthApplication
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)
+			iNdEx += skippy
+		}
+	}
+	if hasFields[0]&uint64(0x00000001) == 0 {
+		return github_com_gogo_protobuf_proto.NewRequiredNotSetError("name")
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
 func skipApplication(dAtA []byte) (n int, err error) {
 	l := len(dAtA)
 	iNdEx := 0
diff --git a/pkg/apiclient/application/application.pb.gw.go b/pkg/apiclient/application/application.pb.gw.go
index 56a1bbc27..80c86e4fc 100644
--- a/pkg/apiclient/application/application.pb.gw.go
+++ b/pkg/apiclient/application/application.pb.gw.go
@@ -1790,6 +1790,150 @@ func request_ApplicationService_PodLogs_1(ctx context.Context, marshaler runtime
 
 }
 
+var (
+	filter_ApplicationService_ListLinks_0 = &utilities.DoubleArray{Encoding: map[string]int{"name": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}}
+)
+
+func request_ApplicationService_ListLinks_0(ctx context.Context, marshaler runtime.Marshaler, client ApplicationServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
+	var protoReq ListAppLinksRequest
+	var metadata runtime.ServerMetadata
+
+	var (
+		val string
+		ok  bool
+		err error
+		_   = err
+	)
+
+	val, ok = pathParams["name"]
+	if !ok {
+		return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "name")
+	}
+
+	protoReq.Name, err = runtime.StringP(val)
+
+	if err != nil {
+		return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "name", err)
+	}
+
+	if err := req.ParseForm(); err != nil {
+		return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
+	}
+	if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ApplicationService_ListLinks_0); err != nil {
+		return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
+	}
+
+	msg, err := client.ListLinks(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
+	return msg, metadata, err
+
+}
+
+func local_request_ApplicationService_ListLinks_0(ctx context.Context, marshaler runtime.Marshaler, server ApplicationServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
+	var protoReq ListAppLinksRequest
+	var metadata runtime.ServerMetadata
+
+	var (
+		val string
+		ok  bool
+		err error
+		_   = err
+	)
+
+	val, ok = pathParams["name"]
+	if !ok {
+		return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "name")
+	}
+
+	protoReq.Name, err = runtime.StringP(val)
+
+	if err != nil {
+		return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "name", err)
+	}
+
+	if err := req.ParseForm(); err != nil {
+		return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
+	}
+	if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ApplicationService_ListLinks_0); err != nil {
+		return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
+	}
+
+	msg, err := server.ListLinks(ctx, &protoReq)
+	return msg, metadata, err
+
+}
+
+var (
+	filter_ApplicationService_ListResourceLinks_0 = &utilities.DoubleArray{Encoding: map[string]int{"name": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}}
+)
+
+func request_ApplicationService_ListResourceLinks_0(ctx context.Context, marshaler runtime.Marshaler, client ApplicationServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
+	var protoReq ApplicationResourceRequest
+	var metadata runtime.ServerMetadata
+
+	var (
+		val string
+		ok  bool
+		err error
+		_   = err
+	)
+
+	val, ok = pathParams["name"]
+	if !ok {
+		return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "name")
+	}
+
+	protoReq.Name, err = runtime.StringP(val)
+
+	if err != nil {
+		return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "name", err)
+	}
+
+	if err := req.ParseForm(); err != nil {
+		return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
+	}
+	if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ApplicationService_ListResourceLinks_0); err != nil {
+		return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
+	}
+
+	msg, err := client.ListResourceLinks(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
+	return msg, metadata, err
+
+}
+
+func local_request_ApplicationService_ListResourceLinks_0(ctx context.Context, marshaler runtime.Marshaler, server ApplicationServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
+	var protoReq ApplicationResourceRequest
+	var metadata runtime.ServerMetadata
+
+	var (
+		val string
+		ok  bool
+		err error
+		_   = err
+	)
+
+	val, ok = pathParams["name"]
+	if !ok {
+		return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "name")
+	}
+
+	protoReq.Name, err = runtime.StringP(val)
+
+	if err != nil {
+		return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "name", err)
+	}
+
+	if err := req.ParseForm(); err != nil {
+		return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
+	}
+	if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ApplicationService_ListResourceLinks_0); err != nil {
+		return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
+	}
+
+	msg, err := server.ListResourceLinks(ctx, &protoReq)
+	return msg, metadata, err
+
+}
+
 // RegisterApplicationServiceHandlerServer registers the http handlers for service ApplicationService to "mux".
 // UnaryRPC     :call ApplicationServiceServer directly.
 // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
@@ -2314,6 +2458,52 @@ func RegisterApplicationServiceHandlerServer(ctx context.Context, mux *runtime.S
 		return
 	})
 
+	mux.Handle("GET", pattern_ApplicationService_ListLinks_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
+		ctx, cancel := context.WithCancel(req.Context())
+		defer cancel()
+		var stream runtime.ServerTransportStream
+		ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
+		inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
+		rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
+		if err != nil {
+			runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
+			return
+		}
+		resp, md, err := local_request_ApplicationService_ListLinks_0(rctx, inboundMarshaler, server, req, pathParams)
+		md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
+		ctx = runtime.NewServerMetadataContext(ctx, md)
+		if err != nil {
+			runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
+			return
+		}
+
+		forward_ApplicationService_ListLinks_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
+
+	})
+
+	mux.Handle("GET", pattern_ApplicationService_ListResourceLinks_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
+		ctx, cancel := context.WithCancel(req.Context())
+		defer cancel()
+		var stream runtime.ServerTransportStream
+		ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
+		inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
+		rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
+		if err != nil {
+			runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
+			return
+		}
+		resp, md, err := local_request_ApplicationService_ListResourceLinks_0(rctx, inboundMarshaler, server, req, pathParams)
+		md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
+		ctx = runtime.NewServerMetadataContext(ctx, md)
+		if err != nil {
+			runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
+			return
+		}
+
+		forward_ApplicationService_ListResourceLinks_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
+
+	})
+
 	return nil
 }
 
@@ -2875,6 +3065,46 @@ func RegisterApplicationServiceHandlerClient(ctx context.Context, mux *runtime.S
 
 	})
 
+	mux.Handle("GET", pattern_ApplicationService_ListLinks_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
+		ctx, cancel := context.WithCancel(req.Context())
+		defer cancel()
+		inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
+		rctx, err := runtime.AnnotateContext(ctx, mux, req)
+		if err != nil {
+			runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
+			return
+		}
+		resp, md, err := request_ApplicationService_ListLinks_0(rctx, inboundMarshaler, client, req, pathParams)
+		ctx = runtime.NewServerMetadataContext(ctx, md)
+		if err != nil {
+			runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
+			return
+		}
+
+		forward_ApplicationService_ListLinks_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
+
+	})
+
+	mux.Handle("GET", pattern_ApplicationService_ListResourceLinks_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
+		ctx, cancel := context.WithCancel(req.Context())
+		defer cancel()
+		inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
+		rctx, err := runtime.AnnotateContext(ctx, mux, req)
+		if err != nil {
+			runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
+			return
+		}
+		resp, md, err := request_ApplicationService_ListResourceLinks_0(rctx, inboundMarshaler, client, req, pathParams)
+		ctx = runtime.NewServerMetadataContext(ctx, md)
+		if err != nil {
+			runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
+			return
+		}
+
+		forward_ApplicationService_ListResourceLinks_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
+
+	})
+
 	return nil
 }
 
@@ -2930,6 +3160,10 @@ var (
 	pattern_ApplicationService_PodLogs_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 4, 1, 0, 4, 1, 5, 5, 2, 6}, []string{"api", "v1", "applications", "name", "pods", "podName", "logs"}, "", runtime.AssumeColonVerbOpt(true)))
 
 	pattern_ApplicationService_PodLogs_1 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 4}, []string{"api", "v1", "applications", "name", "logs"}, "", runtime.AssumeColonVerbOpt(true)))
+
+	pattern_ApplicationService_ListLinks_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 4}, []string{"api", "v1", "applications", "name", "links"}, "", runtime.AssumeColonVerbOpt(true)))
+
+	pattern_ApplicationService_ListResourceLinks_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 4, 2, 5}, []string{"api", "v1", "applications", "name", "resource", "links"}, "", runtime.AssumeColonVerbOpt(true)))
 )
 
 var (
@@ -2984,4 +3218,8 @@ var (
 	forward_ApplicationService_PodLogs_0 = runtime.ForwardResponseStream
 
 	forward_ApplicationService_PodLogs_1 = runtime.ForwardResponseStream
+
+	forward_ApplicationService_ListLinks_0 = runtime.ForwardResponseMessage
+
+	forward_ApplicationService_ListResourceLinks_0 = runtime.ForwardResponseMessage
 )
diff --git a/pkg/apiclient/application/forwarder_overwrite.go b/pkg/apiclient/application/forwarder_overwrite.go
index d37e26bab..9a4bcae10 100644
--- a/pkg/apiclient/application/forwarder_overwrite.go
+++ b/pkg/apiclient/application/forwarder_overwrite.go
@@ -32,6 +32,18 @@ var appFields = map[string]func(app *v1alpha1.Application) interface{}{
 	"status.sync.status":         func(app *v1alpha1.Application) interface{} { return app.Status.Sync.Status },
 	"status.health":              func(app *v1alpha1.Application) interface{} { return app.Status.Health },
 	"status.summary":             func(app *v1alpha1.Application) interface{} { return app.Status.Summary },
+	"status.operationState.startedAt": func(app *v1alpha1.Application) interface{} {
+		if app.Status.OperationState != nil {
+			return app.Status.OperationState.StartedAt
+		}
+		return nil
+	},
+	"status.operationState.finishedAt": func(app *v1alpha1.Application) interface{} {
+		if app.Status.OperationState != nil {
+			return app.Status.OperationState.FinishedAt
+		}
+		return nil
+	},
 	"status.resources": func(app *v1alpha1.Application) interface{} {
 		if len(app.Status.Resources) > 0 {
 			return app.Status.Resources
@@ -69,14 +81,15 @@ func processApplicationListField(v interface{}, fields map[string]interface{}, e
 					continue
 				}
 				value := fn(&app)
+				if value == nil {
+					continue
+				}
 				parts := strings.Split(field, ".")
 				item := converted
 				for i := 0; i < len(parts); i++ {
 					subField := parts[i]
 					if i == len(parts)-1 {
-						if value != nil {
-							item[subField] = value
-						}
+						item[subField] = value
 					} else {
 						if _, ok := item[subField]; !ok {
 							item[subField] = make(map[string]interface{})
diff --git a/pkg/apiclient/project/project.pb.go b/pkg/apiclient/project/project.pb.go
index 97199cdd0..2a89c8c2a 100644
--- a/pkg/apiclient/project/project.pb.go
+++ b/pkg/apiclient/project/project.pb.go
@@ -10,6 +10,7 @@ package project
 import (
 	context "context"
 	fmt "fmt"
+	application "github.com/argoproj/argo-cd/v2/pkg/apiclient/application"
 	v1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
 	proto "github.com/gogo/protobuf/proto"
 	_ "google.golang.org/genproto/googleapis/api/annotations"
@@ -636,6 +637,53 @@ func (m *DetailedProjectsResponse) GetClusters() []*v1alpha1.Cluster {
 	return nil
 }
 
+type ListProjectLinksRequest struct {
+	Name                 string   `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *ListProjectLinksRequest) Reset()         { *m = ListProjectLinksRequest{} }
+func (m *ListProjectLinksRequest) String() string { return proto.CompactTextString(m) }
+func (*ListProjectLinksRequest) ProtoMessage()    {}
+func (*ListProjectLinksRequest) Descriptor() ([]byte, []int) {
+	return fileDescriptor_5f0a51496972c9e2, []int{11}
+}
+func (m *ListProjectLinksRequest) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *ListProjectLinksRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	if deterministic {
+		return xxx_messageInfo_ListProjectLinksRequest.Marshal(b, m, deterministic)
+	} else {
+		b = b[:cap(b)]
+		n, err := m.MarshalToSizedBuffer(b)
+		if err != nil {
+			return nil, err
+		}
+		return b[:n], nil
+	}
+}
+func (m *ListProjectLinksRequest) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_ListProjectLinksRequest.Merge(m, src)
+}
+func (m *ListProjectLinksRequest) XXX_Size() int {
+	return m.Size()
+}
+func (m *ListProjectLinksRequest) XXX_DiscardUnknown() {
+	xxx_messageInfo_ListProjectLinksRequest.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_ListProjectLinksRequest proto.InternalMessageInfo
+
+func (m *ListProjectLinksRequest) GetName() string {
+	if m != nil {
+		return m.Name
+	}
+	return ""
+}
+
 func init() {
 	proto.RegisterType((*ProjectCreateRequest)(nil), "project.ProjectCreateRequest")
 	proto.RegisterType((*ProjectTokenDeleteRequest)(nil), "project.ProjectTokenDeleteRequest")
@@ -648,72 +696,76 @@ func init() {
 	proto.RegisterType((*SyncWindowsResponse)(nil), "project.SyncWindowsResponse")
 	proto.RegisterType((*GlobalProjectsResponse)(nil), "project.GlobalProjectsResponse")
 	proto.RegisterType((*DetailedProjectsResponse)(nil), "project.DetailedProjectsResponse")
+	proto.RegisterType((*ListProjectLinksRequest)(nil), "project.ListProjectLinksRequest")
 }
 
 func init() { proto.RegisterFile("server/project/project.proto", fileDescriptor_5f0a51496972c9e2) }
 
 var fileDescriptor_5f0a51496972c9e2 = []byte{
-	// 950 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x96, 0xcf, 0x6f, 0xe3, 0x44,
-	0x14, 0xc7, 0xe5, 0xa4, 0xcd, 0x6e, 0x27, 0x4b, 0x29, 0xb3, 0xbb, 0xc5, 0x0d, 0xdd, 0x6e, 0x18,
-	0x44, 0x15, 0x15, 0x3a, 0x56, 0x53, 0x90, 0x56, 0x70, 0x62, 0x77, 0xab, 0x80, 0xd4, 0x03, 0xb8,
-	0x20, 0x10, 0x07, 0xd0, 0xc4, 0x7e, 0xca, 0xce, 0xc6, 0xf1, 0x0c, 0x9e, 0x89, 0xb7, 0x21, 0xea,
-	0x05, 0x09, 0x90, 0x38, 0x70, 0x80, 0x13, 0xff, 0x00, 0x27, 0xfe, 0x09, 0x6e, 0x1c, 0x91, 0xf8,
-	0x07, 0x50, 0xc5, 0x1f, 0x82, 0x3c, 0xfe, 0x91, 0x38, 0xa9, 0x11, 0x68, 0xc3, 0x9e, 0x3c, 0x1e,
-	0x3f, 0xbf, 0xef, 0xe7, 0xbd, 0x99, 0x79, 0x6f, 0xd0, 0xae, 0x82, 0x28, 0x86, 0xc8, 0x91, 0x91,
-	0x78, 0x0c, 0x9e, 0xce, 0x9f, 0x54, 0x46, 0x42, 0x0b, 0x7c, 0x2d, 0x7b, 0x6d, 0xed, 0x0e, 0x84,
-	0x18, 0x04, 0xe0, 0x30, 0xc9, 0x1d, 0x16, 0x86, 0x42, 0x33, 0xcd, 0x45, 0xa8, 0x52, 0xb3, 0x16,
-	0x19, 0xde, 0x53, 0x94, 0x0b, 0xf3, 0xd5, 0x13, 0x11, 0x38, 0xf1, 0x91, 0x33, 0x80, 0x10, 0x22,
-	0xa6, 0xc1, 0xcf, 0x6c, 0x4e, 0x07, 0x5c, 0x3f, 0x1a, 0xf7, 0xa9, 0x27, 0x46, 0x0e, 0x8b, 0x06,
-	0x22, 0xf1, 0x6c, 0x06, 0x87, 0x9e, 0xef, 0xc4, 0x5d, 0x47, 0x0e, 0x07, 0xc9, 0xff, 0xca, 0x61,
-	0x52, 0x06, 0xdc, 0x33, 0xfe, 0x9d, 0xf8, 0x88, 0x05, 0xf2, 0x11, 0x5b, 0xf2, 0x46, 0x7e, 0xb0,
-	0xd0, 0xad, 0xf7, 0x53, 0xb6, 0x07, 0x11, 0x30, 0x0d, 0x2e, 0x7c, 0x31, 0x06, 0xa5, 0x71, 0x1f,
-	0xe5, 0xcc, 0xb6, 0xd5, 0xb6, 0x3a, 0xcd, 0xee, 0xbb, 0x74, 0x26, 0x4c, 0x73, 0x61, 0x33, 0xf8,
-	0xdc, 0xf3, 0x69, 0xdc, 0xa5, 0x72, 0x38, 0xa0, 0x89, 0x30, 0x9d, 0x13, 0xa6, 0xb9, 0x30, 0x7d,
-	0x47, 0xca, 0x4c, 0xc7, 0xcd, 0x1d, 0xe3, 0x6d, 0xd4, 0x18, 0x4b, 0x05, 0x91, 0xb6, 0x6b, 0x6d,
-	0xab, 0x73, 0xdd, 0xcd, 0xde, 0xc8, 0x10, 0xed, 0x64, 0xb6, 0x1f, 0x8a, 0x21, 0x84, 0x0f, 0x21,
-	0x80, 0x19, 0x98, 0x5d, 0x06, 0xdb, 0x98, 0xb9, 0xc3, 0x68, 0x2d, 0x12, 0x01, 0x18, 0x67, 0x1b,
-	0xae, 0x19, 0xe3, 0x2d, 0x54, 0xe7, 0x4c, 0xdb, 0xf5, 0xb6, 0xd5, 0xa9, 0xbb, 0xc9, 0x10, 0x6f,
-	0xa2, 0x1a, 0xf7, 0xed, 0x35, 0x63, 0x53, 0xe3, 0x3e, 0xf9, 0xc9, 0x2a, 0xab, 0x95, 0xd3, 0x50,
-	0xad, 0xd6, 0x46, 0x4d, 0x1f, 0x94, 0x17, 0x71, 0x99, 0x04, 0x9a, 0x89, 0xce, 0x4f, 0x15, 0x3c,
-	0xf5, 0x39, 0x9e, 0x5d, 0xb4, 0x01, 0xe7, 0x92, 0x47, 0xa0, 0xde, 0x0b, 0x0d, 0x44, 0xdd, 0x9d,
-	0x4d, 0x64, 0x6c, 0xeb, 0x05, 0xdb, 0xeb, 0xc5, 0xe2, 0x18, 0x34, 0x17, 0x94, 0x14, 0xa1, 0x02,
-	0x7c, 0x0b, 0xad, 0xeb, 0x64, 0x22, 0x63, 0x4a, 0x5f, 0x08, 0x41, 0x37, 0x32, 0xeb, 0x0f, 0xc6,
-	0x10, 0x4d, 0x12, 0xfd, 0x90, 0x8d, 0x20, 0x33, 0x32, 0x63, 0xf2, 0x65, 0xe1, 0xf1, 0x23, 0xe9,
-	0x3f, 0xdb, 0xe5, 0x26, 0xcf, 0xa3, 0xe7, 0x4e, 0x46, 0x52, 0x4f, 0xf2, 0x30, 0xc8, 0x3e, 0xda,
-	0x3a, 0x9b, 0x84, 0xde, 0xc7, 0x3c, 0xf4, 0xc5, 0x13, 0x55, 0x0d, 0x3d, 0x41, 0x37, 0xe7, 0xec,
-	0x8a, 0x2c, 0xf4, 0xd1, 0xb5, 0x27, 0xe9, 0x94, 0x6d, 0xb5, 0xeb, 0x4f, 0xcf, 0x3c, 0xd3, 0x70,
-	0x73, 0xc7, 0xe4, 0x1c, 0x6d, 0xf7, 0x02, 0xd1, 0x67, 0x41, 0x16, 0xcd, 0x4c, 0xfd, 0x33, 0xb4,
-	0xce, 0x35, 0x8c, 0x56, 0xa4, 0x3d, 0x97, 0xaf, 0xd4, 0x2d, 0xf9, 0xb5, 0x8e, 0xec, 0x87, 0xa0,
-	0x19, 0x0f, 0xc0, 0x5f, 0x12, 0x97, 0x68, 0x73, 0x50, 0xc2, 0x5a, 0x39, 0xc5, 0x82, 0xff, 0xf9,
-	0x0d, 0x52, 0xfb, 0xbf, 0xea, 0x41, 0x80, 0x6e, 0x44, 0x20, 0x85, 0xe2, 0x5a, 0x44, 0x1c, 0x94,
-	0x5d, 0x5f, 0x45, 0x4c, 0x6e, 0xee, 0x71, 0xe2, 0x96, 0xbc, 0x63, 0x86, 0xae, 0x7b, 0xc1, 0x58,
-	0x69, 0x88, 0x94, 0xbd, 0x66, 0x94, 0x4e, 0x9e, 0x4e, 0xe9, 0x41, 0xea, 0xcd, 0x2d, 0xdc, 0x76,
-	0x7f, 0x69, 0xa2, 0xcd, 0x2c, 0xca, 0x33, 0x88, 0x62, 0xee, 0x01, 0xfe, 0xce, 0x42, 0xcd, 0xb4,
-	0xc4, 0x98, 0x23, 0x8d, 0x09, 0xcd, 0x3b, 0x45, 0x65, 0x11, 0x6a, 0xdd, 0xb9, 0xd2, 0xa6, 0x38,
-	0x46, 0xf7, 0xbe, 0xfa, 0xe3, 0xaf, 0x1f, 0x6b, 0x5d, 0x72, 0x68, 0xfa, 0x46, 0x7c, 0x94, 0xf7,
-	0x1e, 0xe5, 0x4c, 0xb3, 0xd1, 0x85, 0x93, 0x14, 0x1f, 0xe5, 0x4c, 0x93, 0xc7, 0x85, 0x63, 0xca,
-	0xc5, 0x5b, 0xd6, 0x01, 0xfe, 0xc6, 0x42, 0xcd, 0xb4, 0xba, 0xfe, 0x13, 0x4c, 0xa9, 0xfe, 0xb6,
-	0xb6, 0x0b, 0x9b, 0xf2, 0x61, 0x7e, 0xdb, 0x50, 0xbc, 0x79, 0x70, 0xfc, 0x9f, 0x28, 0x9c, 0x29,
-	0x67, 0xfa, 0x02, 0x7f, 0x6f, 0xa1, 0x46, 0x1a, 0x33, 0x5e, 0x0a, 0xb6, 0x9c, 0x8b, 0x95, 0x6d,
-	0x3b, 0xf2, 0x92, 0x01, 0xbe, 0x4d, 0xb6, 0x16, 0x81, 0x93, 0xcc, 0x7c, 0x6d, 0xa1, 0xb5, 0x53,
-	0xae, 0x34, 0xbe, 0xbd, 0x88, 0x63, 0xca, 0x54, 0xeb, 0x74, 0x55, 0x18, 0x89, 0x08, 0xb1, 0x0d,
-	0x0a, 0xc6, 0x4b, 0x28, 0xf8, 0x1c, 0xe1, 0x1e, 0xe8, 0x85, 0x3a, 0x50, 0x05, 0xf5, 0x72, 0x31,
-	0x5d, 0x55, 0x38, 0x48, 0xc7, 0x28, 0x11, 0xdc, 0x5e, 0x5e, 0xa5, 0xa4, 0xd4, 0x5e, 0x38, 0x7e,
-	0xf6, 0x27, 0xfe, 0xd6, 0x42, 0xf5, 0x1e, 0x54, 0x6a, 0xad, 0x6e, 0x1d, 0xee, 0x1a, 0xa4, 0x1d,
-	0xfc, 0x62, 0x05, 0x12, 0x9e, 0xa2, 0x17, 0x7a, 0xa0, 0xcb, 0x65, 0xb8, 0x0a, 0xeb, 0x6e, 0x31,
-	0x7d, 0x75, 0xd9, 0x26, 0xd4, 0xa8, 0x75, 0xf0, 0x7e, 0x55, 0x02, 0xd2, 0xba, 0x57, 0x2c, 0xc0,
-	0xcf, 0x16, 0x6a, 0xa4, 0xad, 0x72, 0x79, 0x67, 0x96, 0x5a, 0xe8, 0x0a, 0x33, 0x72, 0x6c, 0x18,
-	0x0f, 0x5b, 0x9d, 0xca, 0xa3, 0x44, 0x47, 0xa0, 0x99, 0xcf, 0x34, 0xa3, 0x06, 0x3a, 0xd9, 0xb1,
-	0x9f, 0xa0, 0x46, 0x7a, 0x50, 0xab, 0x52, 0x53, 0x75, 0x70, 0xb3, 0xfc, 0x1f, 0x54, 0xe6, 0xff,
-	0x31, 0x42, 0xc9, 0x2e, 0x3d, 0x89, 0x21, 0xac, 0x4e, 0xfc, 0x1d, 0x9a, 0xde, 0x5d, 0x93, 0x08,
-	0x69, 0x72, 0x77, 0xa5, 0xf1, 0x11, 0x35, 0xbf, 0x98, 0x1d, 0xbe, 0x6f, 0x44, 0xda, 0x78, 0xaf,
-	0x2a, 0xed, 0x90, 0x7a, 0x9f, 0xa2, 0x9b, 0x3d, 0xd0, 0x73, 0xdd, 0xfe, 0x4c, 0x27, 0xa9, 0xdf,
-	0x29, 0x44, 0x17, 0x2f, 0x0c, 0xad, 0xdd, 0xab, 0x3e, 0x15, 0xc1, 0xbd, 0x66, 0x74, 0x5f, 0xc5,
-	0xaf, 0x54, 0xe9, 0xaa, 0x49, 0xe8, 0x65, 0xcd, 0xfe, 0xfe, 0xfd, 0xdf, 0x2e, 0xf7, 0xac, 0xdf,
-	0x2f, 0xf7, 0xac, 0x3f, 0x2f, 0xf7, 0xac, 0x4f, 0xdf, 0xf8, 0x77, 0x17, 0x6d, 0x2f, 0xe0, 0x10,
-	0x16, 0xf7, 0xfd, 0x7e, 0xc3, 0xdc, 0xab, 0x8f, 0xff, 0x0e, 0x00, 0x00, 0xff, 0xff, 0x0c, 0x1d,
-	0x2d, 0x85, 0x10, 0x0c, 0x00, 0x00,
+	// 1007 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x57, 0xcf, 0x6f, 0xe3, 0x44,
+	0x14, 0x96, 0x93, 0x36, 0xbb, 0x7d, 0x2d, 0xa5, 0xcc, 0xee, 0x76, 0xdd, 0xd0, 0x1f, 0x61, 0xd0,
+	0x56, 0x51, 0xa1, 0xb6, 0x9a, 0x82, 0xb4, 0x82, 0x13, 0xdb, 0xad, 0x02, 0x52, 0x0f, 0xe0, 0x82,
+	0x40, 0x1c, 0x40, 0x8e, 0xfd, 0x94, 0x9d, 0x8d, 0x63, 0x1b, 0xcf, 0x24, 0xdb, 0x10, 0xf5, 0x82,
+	0x04, 0x48, 0x1c, 0x38, 0xc0, 0x89, 0x0b, 0x47, 0xfe, 0x0f, 0x6e, 0x1c, 0x91, 0xf8, 0x07, 0x50,
+	0xc5, 0x1f, 0x82, 0x66, 0x3c, 0x76, 0xe2, 0xa4, 0xe6, 0x87, 0x1a, 0x38, 0x65, 0x3c, 0x7e, 0xfe,
+	0xbe, 0xef, 0xbd, 0x79, 0xf3, 0xcd, 0x04, 0xb6, 0x39, 0x26, 0x43, 0x4c, 0xec, 0x38, 0x89, 0x9e,
+	0xa2, 0x27, 0xb2, 0x5f, 0x2b, 0x4e, 0x22, 0x11, 0x91, 0x5b, 0xfa, 0xb1, 0xbe, 0xdd, 0x8d, 0xa2,
+	0x6e, 0x80, 0xb6, 0x1b, 0x33, 0xdb, 0x0d, 0xc3, 0x48, 0xb8, 0x82, 0x45, 0x21, 0x4f, 0xc3, 0xea,
+	0xb4, 0xf7, 0x90, 0x5b, 0x2c, 0x52, 0x6f, 0xbd, 0x28, 0x41, 0x7b, 0x78, 0x64, 0x77, 0x31, 0xc4,
+	0xc4, 0x15, 0xe8, 0xeb, 0x98, 0xb3, 0x2e, 0x13, 0x4f, 0x06, 0x1d, 0xcb, 0x8b, 0xfa, 0xb6, 0x9b,
+	0x74, 0x23, 0x89, 0xac, 0x06, 0x87, 0x9e, 0x6f, 0x0f, 0x5b, 0x76, 0xdc, 0xeb, 0xca, 0xef, 0xb9,
+	0xed, 0xc6, 0x71, 0xc0, 0x3c, 0x85, 0x6f, 0x0f, 0x8f, 0xdc, 0x20, 0x7e, 0xe2, 0xce, 0xa3, 0x9d,
+	0xfc, 0x0d, 0x9a, 0xce, 0x6a, 0x1a, 0x6b, 0x6a, 0x9c, 0x82, 0xd0, 0xef, 0x0c, 0xb8, 0xfb, 0x6e,
+	0x9a, 0xe0, 0x49, 0x82, 0xae, 0x40, 0x07, 0x3f, 0x1b, 0x20, 0x17, 0xa4, 0x03, 0x59, 0xe2, 0xa6,
+	0xd1, 0x30, 0x9a, 0xab, 0xad, 0xb7, 0xad, 0x09, 0x9f, 0x95, 0xf1, 0xa9, 0xc1, 0xa7, 0x9e, 0x6f,
+	0x0d, 0x5b, 0x56, 0xdc, 0xeb, 0x5a, 0x52, 0xbd, 0x35, 0xcd, 0x92, 0xa9, 0xb7, 0xde, 0x8a, 0x63,
+	0xcd, 0xe3, 0x64, 0xc0, 0x64, 0x13, 0x6a, 0x83, 0x98, 0x63, 0x22, 0xcc, 0x4a, 0xc3, 0x68, 0xde,
+	0x76, 0xf4, 0x13, 0xed, 0xc1, 0x96, 0x8e, 0x7d, 0x3f, 0xea, 0x61, 0xf8, 0x18, 0x03, 0x9c, 0x08,
+	0x33, 0x8b, 0xc2, 0x56, 0x26, 0x70, 0x04, 0x96, 0x92, 0x28, 0x40, 0x05, 0xb6, 0xe2, 0xa8, 0x31,
+	0xd9, 0x80, 0x2a, 0x73, 0x85, 0x59, 0x6d, 0x18, 0xcd, 0xaa, 0x23, 0x87, 0x64, 0x1d, 0x2a, 0xcc,
+	0x37, 0x97, 0x54, 0x4c, 0x85, 0xf9, 0xf4, 0x07, 0xa3, 0xc8, 0x56, 0x2c, 0x43, 0x39, 0x5b, 0x03,
+	0x56, 0x7d, 0xe4, 0x5e, 0xc2, 0x62, 0x99, 0xa8, 0x26, 0x9d, 0x9e, 0xca, 0xf5, 0x54, 0xa7, 0xf4,
+	0x6c, 0xc3, 0x0a, 0x5e, 0xc4, 0x2c, 0x41, 0xfe, 0x4e, 0xa8, 0x44, 0x54, 0x9d, 0xc9, 0x84, 0xd6,
+	0xb6, 0x9c, 0x6b, 0x7b, 0x35, 0x5f, 0x1c, 0x25, 0xcd, 0x41, 0x1e, 0x47, 0x21, 0x47, 0x72, 0x17,
+	0x96, 0x85, 0x9c, 0xd0, 0x9a, 0xd2, 0x07, 0x4a, 0x61, 0x4d, 0x47, 0xbf, 0x37, 0xc0, 0x64, 0x24,
+	0xf9, 0x43, 0xb7, 0x8f, 0x3a, 0x48, 0x8d, 0xe9, 0xe7, 0x39, 0xe2, 0x07, 0xb1, 0xff, 0xff, 0x2e,
+	0x37, 0x7d, 0x1e, 0x9e, 0x3b, 0xed, 0xc7, 0x62, 0x94, 0xa5, 0x41, 0xf7, 0x61, 0xe3, 0x7c, 0x14,
+	0x7a, 0x1f, 0xb2, 0xd0, 0x8f, 0x9e, 0xf1, 0x72, 0xd1, 0x23, 0xb8, 0x33, 0x15, 0x97, 0x57, 0xa1,
+	0x03, 0xb7, 0x9e, 0xa5, 0x53, 0xa6, 0xd1, 0xa8, 0xde, 0x5c, 0xf3, 0x84, 0xc3, 0xc9, 0x80, 0xe9,
+	0x05, 0x6c, 0xb6, 0x83, 0xa8, 0xe3, 0x06, 0x3a, 0x9b, 0x09, 0xfb, 0x27, 0xb0, 0xcc, 0x04, 0xf6,
+	0x17, 0xc4, 0x3d, 0x55, 0xaf, 0x14, 0x96, 0xfe, 0x5c, 0x05, 0xf3, 0x31, 0x0a, 0x97, 0x05, 0xe8,
+	0xcf, 0x91, 0xc7, 0xb0, 0xde, 0x2d, 0xc8, 0x5a, 0xb8, 0x8a, 0x19, 0xfc, 0xe9, 0x06, 0xa9, 0xfc,
+	0x57, 0x7e, 0x10, 0xc0, 0x5a, 0x82, 0x71, 0xc4, 0x99, 0x88, 0x12, 0x86, 0xdc, 0xac, 0x2e, 0x22,
+	0x27, 0x27, 0x43, 0x1c, 0x39, 0x05, 0x74, 0xe2, 0xc2, 0x6d, 0x2f, 0x18, 0x70, 0x81, 0x09, 0x37,
+	0x97, 0x14, 0xd3, 0xe9, 0xcd, 0x98, 0x4e, 0x52, 0x34, 0x27, 0x87, 0xa5, 0x87, 0x70, 0xff, 0x8c,
+	0x71, 0xa1, 0x13, 0x3d, 0x63, 0x61, 0x8f, 0x67, 0x1b, 0xee, 0x9a, 0x3e, 0x6f, 0xfd, 0xb8, 0x06,
+	0xeb, 0x3a, 0xf6, 0x1c, 0x93, 0x21, 0xf3, 0x90, 0x7c, 0x63, 0xc0, 0x6a, 0xea, 0x48, 0xca, 0x01,
+	0x08, 0xb5, 0xb2, 0xd3, 0xa9, 0xd4, 0xb3, 0xea, 0x3b, 0xd7, 0xc6, 0xe4, 0xbb, 0xee, 0xe1, 0x17,
+	0xbf, 0xfd, 0xf1, 0x7d, 0xa5, 0x45, 0x0f, 0xd5, 0x59, 0x35, 0x3c, 0xca, 0xce, 0x3b, 0x6e, 0x8f,
+	0xf5, 0xe8, 0xd2, 0x96, 0x5e, 0xc5, 0xed, 0xb1, 0xfc, 0xb9, 0xb4, 0x95, 0xbb, 0xbc, 0x61, 0x1c,
+	0x90, 0xaf, 0x0c, 0x58, 0x4d, 0xcd, 0xf8, 0xaf, 0xc4, 0x14, 0xec, 0xba, 0xbe, 0x99, 0xc7, 0x14,
+	0xf7, 0xfe, 0x9b, 0x4a, 0xc5, 0xeb, 0x07, 0xc7, 0xff, 0x4a, 0x85, 0x3d, 0x66, 0xae, 0xb8, 0x24,
+	0xdf, 0x1a, 0x50, 0x4b, 0x73, 0x26, 0x73, 0xc9, 0x16, 0x6b, 0xb1, 0xb0, 0x2e, 0xa5, 0x2f, 0x2a,
+	0xc1, 0xf7, 0xe8, 0xc6, 0xac, 0x60, 0x59, 0x99, 0x2f, 0x0d, 0x58, 0x92, 0x2b, 0x4d, 0xee, 0xcd,
+	0xca, 0x51, 0xae, 0x56, 0x3f, 0x5b, 0x94, 0x0c, 0x49, 0x42, 0x4d, 0x25, 0x85, 0x90, 0x39, 0x29,
+	0xe4, 0x02, 0x48, 0x1b, 0xc5, 0x8c, 0x6d, 0x94, 0x89, 0x7a, 0x29, 0x9f, 0x2e, 0xf3, 0x19, 0xda,
+	0x54, 0x4c, 0x94, 0x34, 0xe6, 0x57, 0x49, 0x76, 0xec, 0xa5, 0xed, 0xeb, 0x2f, 0xc9, 0xd7, 0x06,
+	0x54, 0xdb, 0x58, 0xca, 0xb5, 0xb8, 0x75, 0xd8, 0x53, 0x92, 0xb6, 0xc8, 0xfd, 0x12, 0x49, 0x64,
+	0x0c, 0x2f, 0xb4, 0x51, 0x14, 0x5d, 0xbb, 0x4c, 0xd6, 0x5e, 0x3e, 0x7d, 0xbd, 0xcb, 0x53, 0x4b,
+	0xb1, 0x35, 0xc9, 0x7e, 0x59, 0x01, 0x52, 0x9b, 0xcc, 0x17, 0xe0, 0x27, 0x03, 0x6a, 0xe9, 0xc9,
+	0x3a, 0xdf, 0x99, 0x85, 0x13, 0x77, 0x81, 0x15, 0x39, 0x56, 0x1a, 0x0f, 0xeb, 0xcd, 0xd2, 0xad,
+	0x64, 0xf5, 0x51, 0xb8, 0xbe, 0x2b, 0x5c, 0x4b, 0x89, 0x96, 0x1d, 0xfb, 0x11, 0xd4, 0xd2, 0x8d,
+	0x5a, 0x56, 0x9a, 0xb2, 0x8d, 0xab, 0xeb, 0x7f, 0x50, 0x5a, 0xff, 0xa7, 0x00, 0xb2, 0x4b, 0x4f,
+	0x87, 0x18, 0x96, 0x17, 0x7e, 0xc7, 0x4a, 0xef, 0xcb, 0x32, 0x43, 0x4b, 0xde, 0x97, 0xad, 0xe1,
+	0x91, 0xa5, 0x3e, 0x51, 0x1d, 0xbe, 0xaf, 0x48, 0x1a, 0x64, 0xb7, 0xac, 0xec, 0x98, 0xa2, 0x8f,
+	0xe1, 0x4e, 0x1b, 0xc5, 0xd4, 0xe5, 0xe0, 0x5c, 0xc8, 0xd2, 0x6f, 0xe5, 0xa4, 0xb3, 0xf7, 0x8b,
+	0xfa, 0xf6, 0x75, 0xaf, 0xf2, 0xe4, 0x5e, 0x51, 0xbc, 0x0f, 0xc8, 0xcb, 0x65, 0xbc, 0x7c, 0x14,
+	0x7a, 0xfa, 0x6e, 0x40, 0x62, 0x58, 0x91, 0x62, 0x95, 0xad, 0x93, 0x46, 0x8e, 0x5b, 0xe2, 0xf8,
+	0xf5, 0x7a, 0x61, 0x21, 0xf5, 0x2b, 0xcd, 0xfb, 0x40, 0xf1, 0xee, 0x91, 0x9d, 0x32, 0xde, 0x40,
+	0x86, 0x3f, 0x7a, 0xf4, 0xcb, 0xd5, 0xae, 0xf1, 0xeb, 0xd5, 0xae, 0xf1, 0xfb, 0xd5, 0xae, 0xf1,
+	0xf1, 0x6b, 0xff, 0xec, 0xef, 0x84, 0x17, 0x30, 0x0c, 0xf3, 0x7f, 0x35, 0x9d, 0x9a, 0xba, 0xf8,
+	0x1f, 0xff, 0x19, 0x00, 0x00, 0xff, 0xff, 0x28, 0x9d, 0xea, 0xa8, 0xf6, 0x0c, 0x00, 0x00,
 }
 
 // Reference imports to suppress errors if they are not otherwise used.
@@ -750,6 +802,8 @@ type ProjectServiceClient interface {
 	ListEvents(ctx context.Context, in *ProjectQuery, opts ...grpc.CallOption) (*v1.EventList, error)
 	// GetSchedulesState returns true if there are any active sync syncWindows
 	GetSyncWindowsState(ctx context.Context, in *SyncWindowsQuery, opts ...grpc.CallOption) (*SyncWindowsResponse, error)
+	// ListLinks returns all deep links for the particular project
+	ListLinks(ctx context.Context, in *ListProjectLinksRequest, opts ...grpc.CallOption) (*application.LinksResponse, error)
 }
 
 type projectServiceClient struct {
@@ -859,6 +913,15 @@ func (c *projectServiceClient) GetSyncWindowsState(ctx context.Context, in *Sync
 	return out, nil
 }
 
+func (c *projectServiceClient) ListLinks(ctx context.Context, in *ListProjectLinksRequest, opts ...grpc.CallOption) (*application.LinksResponse, error) {
+	out := new(application.LinksResponse)
+	err := c.cc.Invoke(ctx, "/project.ProjectService/ListLinks", in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
 // ProjectServiceServer is the server API for ProjectService service.
 type ProjectServiceServer interface {
 	// Create a new project token
@@ -883,6 +946,8 @@ type ProjectServiceServer interface {
 	ListEvents(context.Context, *ProjectQuery) (*v1.EventList, error)
 	// GetSchedulesState returns true if there are any active sync syncWindows
 	GetSyncWindowsState(context.Context, *SyncWindowsQuery) (*SyncWindowsResponse, error)
+	// ListLinks returns all deep links for the particular project
+	ListLinks(context.Context, *ListProjectLinksRequest) (*application.LinksResponse, error)
 }
 
 // UnimplementedProjectServiceServer can be embedded to have forward compatible implementations.
@@ -922,6 +987,9 @@ func (*UnimplementedProjectServiceServer) ListEvents(ctx context.Context, req *P
 func (*UnimplementedProjectServiceServer) GetSyncWindowsState(ctx context.Context, req *SyncWindowsQuery) (*SyncWindowsResponse, error) {
 	return nil, status.Errorf(codes.Unimplemented, "method GetSyncWindowsState not implemented")
 }
+func (*UnimplementedProjectServiceServer) ListLinks(ctx context.Context, req *ListProjectLinksRequest) (*application.LinksResponse, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method ListLinks not implemented")
+}
 
 func RegisterProjectServiceServer(s *grpc.Server, srv ProjectServiceServer) {
 	s.RegisterService(&_ProjectService_serviceDesc, srv)
@@ -1125,6 +1193,24 @@ func _ProjectService_GetSyncWindowsState_Handler(srv interface{}, ctx context.Co
 	return interceptor(ctx, in, info, handler)
 }
 
+func _ProjectService_ListLinks_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(ListProjectLinksRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(ProjectServiceServer).ListLinks(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/project.ProjectService/ListLinks",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(ProjectServiceServer).ListLinks(ctx, req.(*ListProjectLinksRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
 var _ProjectService_serviceDesc = grpc.ServiceDesc{
 	ServiceName: "project.ProjectService",
 	HandlerType: (*ProjectServiceServer)(nil),
@@ -1173,6 +1259,10 @@ var _ProjectService_serviceDesc = grpc.ServiceDesc{
 			MethodName: "GetSyncWindowsState",
 			Handler:    _ProjectService_GetSyncWindowsState_Handler,
 		},
+		{
+			MethodName: "ListLinks",
+			Handler:    _ProjectService_ListLinks_Handler,
+		},
 	},
 	Streams:  []grpc.StreamDesc{},
 	Metadata: "server/project/project.proto",
@@ -1671,6 +1761,40 @@ func (m *DetailedProjectsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error
 	return len(dAtA) - i, nil
 }
 
+func (m *ListProjectLinksRequest) Marshal() (dAtA []byte, err error) {
+	size := m.Size()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalToSizedBuffer(dAtA[:size])
+	if err != nil {
+		return nil, err
+	}
+	return dAtA[:n], nil
+}
+
+func (m *ListProjectLinksRequest) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *ListProjectLinksRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	if m.XXX_unrecognized != nil {
+		i -= len(m.XXX_unrecognized)
+		copy(dAtA[i:], m.XXX_unrecognized)
+	}
+	if len(m.Name) > 0 {
+		i -= len(m.Name)
+		copy(dAtA[i:], m.Name)
+		i = encodeVarintProject(dAtA, i, uint64(len(m.Name)))
+		i--
+		dAtA[i] = 0xa
+	}
+	return len(dAtA) - i, nil
+}
+
 func encodeVarintProject(dAtA []byte, offset int, v uint64) int {
 	offset -= sovProject(v)
 	base := offset
@@ -1905,6 +2029,22 @@ func (m *DetailedProjectsResponse) Size() (n int) {
 	return n
 }
 
+func (m *ListProjectLinksRequest) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	l = len(m.Name)
+	if l > 0 {
+		n += 1 + l + sovProject(uint64(l))
+	}
+	if m.XXX_unrecognized != nil {
+		n += len(m.XXX_unrecognized)
+	}
+	return n
+}
+
 func sovProject(x uint64) (n int) {
 	return (math_bits.Len64(x|1) + 6) / 7
 }
@@ -3128,6 +3268,89 @@ func (m *DetailedProjectsResponse) Unmarshal(dAtA []byte) error {
 	}
 	return nil
 }
+func (m *ListProjectLinksRequest) Unmarshal(dAtA []byte) error {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowProject
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= uint64(b&0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: ListProjectLinksRequest: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: ListProjectLinksRequest: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowProject
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthProject
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthProject
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.Name = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		default:
+			iNdEx = preIndex
+			skippy, err := skipProject(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if (skippy < 0) || (iNdEx+skippy) < 0 {
+				return ErrInvalidLengthProject
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)
+			iNdEx += skippy
+		}
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
 func skipProject(dAtA []byte) (n int, err error) {
 	l := len(dAtA)
 	iNdEx := 0
diff --git a/pkg/apiclient/project/project.pb.gw.go b/pkg/apiclient/project/project.pb.gw.go
index 8ae2e95a6..f672aa23c 100644
--- a/pkg/apiclient/project/project.pb.gw.go
+++ b/pkg/apiclient/project/project.pb.gw.go
@@ -705,6 +705,60 @@ func local_request_ProjectService_GetSyncWindowsState_0(ctx context.Context, mar
 
 }
 
+func request_ProjectService_ListLinks_0(ctx context.Context, marshaler runtime.Marshaler, client ProjectServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
+	var protoReq ListProjectLinksRequest
+	var metadata runtime.ServerMetadata
+
+	var (
+		val string
+		ok  bool
+		err error
+		_   = err
+	)
+
+	val, ok = pathParams["name"]
+	if !ok {
+		return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "name")
+	}
+
+	protoReq.Name, err = runtime.String(val)
+
+	if err != nil {
+		return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "name", err)
+	}
+
+	msg, err := client.ListLinks(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
+	return msg, metadata, err
+
+}
+
+func local_request_ProjectService_ListLinks_0(ctx context.Context, marshaler runtime.Marshaler, server ProjectServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
+	var protoReq ListProjectLinksRequest
+	var metadata runtime.ServerMetadata
+
+	var (
+		val string
+		ok  bool
+		err error
+		_   = err
+	)
+
+	val, ok = pathParams["name"]
+	if !ok {
+		return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "name")
+	}
+
+	protoReq.Name, err = runtime.String(val)
+
+	if err != nil {
+		return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "name", err)
+	}
+
+	msg, err := server.ListLinks(ctx, &protoReq)
+	return msg, metadata, err
+
+}
+
 // RegisterProjectServiceHandlerServer registers the http handlers for service ProjectService to "mux".
 // UnaryRPC     :call ProjectServiceServer directly.
 // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
@@ -964,6 +1018,29 @@ func RegisterProjectServiceHandlerServer(ctx context.Context, mux *runtime.Serve
 
 	})
 
+	mux.Handle("GET", pattern_ProjectService_ListLinks_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
+		ctx, cancel := context.WithCancel(req.Context())
+		defer cancel()
+		var stream runtime.ServerTransportStream
+		ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
+		inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
+		rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
+		if err != nil {
+			runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
+			return
+		}
+		resp, md, err := local_request_ProjectService_ListLinks_0(rctx, inboundMarshaler, server, req, pathParams)
+		md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
+		ctx = runtime.NewServerMetadataContext(ctx, md)
+		if err != nil {
+			runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
+			return
+		}
+
+		forward_ProjectService_ListLinks_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
+
+	})
+
 	return nil
 }
 
@@ -1225,6 +1302,26 @@ func RegisterProjectServiceHandlerClient(ctx context.Context, mux *runtime.Serve
 
 	})
 
+	mux.Handle("GET", pattern_ProjectService_ListLinks_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
+		ctx, cancel := context.WithCancel(req.Context())
+		defer cancel()
+		inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
+		rctx, err := runtime.AnnotateContext(ctx, mux, req)
+		if err != nil {
+			runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
+			return
+		}
+		resp, md, err := request_ProjectService_ListLinks_0(rctx, inboundMarshaler, client, req, pathParams)
+		ctx = runtime.NewServerMetadataContext(ctx, md)
+		if err != nil {
+			runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
+			return
+		}
+
+		forward_ProjectService_ListLinks_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
+
+	})
+
 	return nil
 }
 
@@ -1250,6 +1347,8 @@ var (
 	pattern_ProjectService_ListEvents_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 4}, []string{"api", "v1", "projects", "name", "events"}, "", runtime.AssumeColonVerbOpt(true)))
 
 	pattern_ProjectService_GetSyncWindowsState_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 4}, []string{"api", "v1", "projects", "name", "syncwindows"}, "", runtime.AssumeColonVerbOpt(true)))
+
+	pattern_ProjectService_ListLinks_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 4}, []string{"api", "v1", "projects", "name", "links"}, "", runtime.AssumeColonVerbOpt(true)))
 )
 
 var (
@@ -1274,4 +1373,6 @@ var (
 	forward_ProjectService_ListEvents_0 = runtime.ForwardResponseMessage
 
 	forward_ProjectService_GetSyncWindowsState_0 = runtime.ForwardResponseMessage
+
+	forward_ProjectService_ListLinks_0 = runtime.ForwardResponseMessage
 )
diff --git a/pkg/apis/application/v1alpha1/app_project_types.go b/pkg/apis/application/v1alpha1/app_project_types.go
index 0d55f51a7..5243ab799 100644
--- a/pkg/apis/application/v1alpha1/app_project_types.go
+++ b/pkg/apis/application/v1alpha1/app_project_types.go
@@ -187,6 +187,10 @@ func (p *AppProject) ValidateProject() error {
 
 	srcRepos := make(map[string]bool)
 	for _, src := range p.Spec.SourceRepos {
+		if src == "!*" {
+			return status.Errorf(codes.InvalidArgument, "source repository has an invalid format, '!*'")
+		}
+
 		if _, ok := srcRepos[src]; ok {
 			return status.Errorf(codes.InvalidArgument, "source repository '%s' already added", src)
 		}
@@ -365,7 +369,7 @@ func (proj *AppProject) RemoveFinalizer() {
 }
 
 func globMatch(pattern string, val string, allowNegation bool, separators ...rune) bool {
-	if allowNegation && isDenyDestination(pattern) {
+	if allowNegation && isDenyPattern(pattern) {
 		return !glob.Match(pattern[1:], val, separators...)
 	}
 
@@ -378,13 +382,26 @@ func globMatch(pattern string, val string, allowNegation bool, separators ...run
 // IsSourcePermitted validates if the provided application's source is a one of the allowed sources for the project.
 func (proj AppProject) IsSourcePermitted(src ApplicationSource) bool {
 	srcNormalized := git.NormalizeGitURL(src.RepoURL)
+
+	var normalized string
+	anySourceMatched := false
+
 	for _, repoURL := range proj.Spec.SourceRepos {
-		normalized := git.NormalizeGitURL(repoURL)
-		if globMatch(normalized, srcNormalized, false, '/') {
-			return true
+		if isDenyPattern(repoURL) {
+			normalized = "!" + git.NormalizeGitURL(strings.TrimPrefix(repoURL, "!"))
+		} else {
+			normalized = git.NormalizeGitURL(repoURL)
+		}
+
+		matched := globMatch(normalized, srcNormalized, true, '/')
+		if matched {
+			anySourceMatched = true
+		} else if !matched && isDenyPattern(normalized) {
+			return false
 		}
 	}
-	return false
+
+	return anySourceMatched
 }
 
 // IsDestinationPermitted validates if the provided application's destination is one of the allowed destinations for the project
@@ -420,7 +437,7 @@ func (proj AppProject) isDestinationMatched(dst ApplicationDestination) bool {
 		matched := (dstServerMatched || dstNameMatched) && dstNamespaceMatched
 		if matched {
 			anyDestinationMatched = true
-		} else if ((!dstNameMatched && isDenyDestination(item.Name)) || (!dstServerMatched && isDenyDestination(item.Server))) || (!dstNamespaceMatched && isDenyDestination(item.Namespace)) {
+		} else if ((!dstNameMatched && isDenyPattern(item.Name)) || (!dstServerMatched && isDenyPattern(item.Server))) || (!dstNamespaceMatched && isDenyPattern(item.Namespace)) {
 			noDenyDestinationsMatched = false
 		}
 	}
@@ -428,7 +445,7 @@ func (proj AppProject) isDestinationMatched(dst ApplicationDestination) bool {
 	return anyDestinationMatched && noDenyDestinationsMatched
 }
 
-func isDenyDestination(pattern string) bool {
+func isDenyPattern(pattern string) bool {
 	return strings.HasPrefix(pattern, "!")
 }
 
diff --git a/reposerver/repository/repository.go b/reposerver/repository/repository.go
index ca2059ae8..80d81f42b 100644
--- a/reposerver/repository/repository.go
+++ b/reposerver/repository/repository.go
@@ -818,6 +818,32 @@ func runHelmBuild(appPath string, h helm.Helm) error {
 	return os.WriteFile(markerFile, []byte("marker"), 0644)
 }
 
+func populateRequestRepos(appPath string, q *apiclient.ManifestRequest) error {
+	repos, err := getHelmDependencyRepos(appPath)
+	if err != nil {
+		return err
+	}
+
+	for _, r := range repos {
+		if !repoExists(r.Repo, q.Repos) {
+			repositoryCredential := getRepoCredential(q.HelmRepoCreds, r.Repo)
+			if repositoryCredential != nil {
+				if repositoryCredential.EnableOCI {
+					r.Repo = strings.TrimPrefix(r.Repo, ociPrefix)
+				}
+				r.EnableOCI = repositoryCredential.EnableOCI
+				r.Password = repositoryCredential.Password
+				r.Username = repositoryCredential.Username
+				r.SSHPrivateKey = repositoryCredential.SSHPrivateKey
+				r.TLSClientCertData = repositoryCredential.TLSClientCertData
+				r.TLSClientCertKey = repositoryCredential.TLSClientCertKey
+			}
+			q.Repos = append(q.Repos, r)
+		}
+	}
+	return nil
+}
+
 func helmTemplate(appPath string, repoRoot string, env *v1alpha1.Env, q *apiclient.ManifestRequest, isLocal bool) ([]*unstructured.Unstructured, error) {
 	concurrencyAllowed := isConcurrencyAllowed(appPath)
 	if !concurrencyAllowed {
@@ -914,24 +940,8 @@ func helmTemplate(appPath string, repoRoot string, env *v1alpha1.Env, q *apiclie
 		templateOpts.SetString[i] = env.Envsubst(j)
 	}
 
-	repos, err := getHelmDependencyRepos(appPath)
-	if err != nil {
-		return nil, err
-	}
-
-	for _, r := range repos {
-		if !repoExists(r.Repo, q.Repos) {
-			repositoryCredential := getRepoCredential(q.HelmRepoCreds, r.Repo)
-			if repositoryCredential != nil {
-				r.EnableOCI = repositoryCredential.EnableOCI
-				r.Password = repositoryCredential.Password
-				r.Username = repositoryCredential.Username
-				r.SSHPrivateKey = repositoryCredential.SSHPrivateKey
-				r.TLSClientCertData = repositoryCredential.TLSClientCertData
-				r.TLSClientCertKey = repositoryCredential.TLSClientCertKey
-			}
-			q.Repos = append(q.Repos, r)
-		}
+	if err := populateRequestRepos(appPath, q); err != nil {
+		return nil, fmt.Errorf("failed parsing dependencies: %v", err)
 	}
 
 	var proxy string
diff --git a/server/application/application.go b/server/application/application.go
index 686b971d0..b56db0941 100644
--- a/server/application/application.go
+++ b/server/application/application.go
@@ -42,6 +42,7 @@ import (
 	applisters "github.com/argoproj/argo-cd/v2/pkg/client/listers/application/v1alpha1"
 	"github.com/argoproj/argo-cd/v2/reposerver/apiclient"
 	servercache "github.com/argoproj/argo-cd/v2/server/cache"
+	"github.com/argoproj/argo-cd/v2/server/deeplinks"
 	"github.com/argoproj/argo-cd/v2/server/rbacpolicy"
 	"github.com/argoproj/argo-cd/v2/util/argo"
 	argoutil "github.com/argoproj/argo-cd/v2/util/argo"
@@ -1892,6 +1893,64 @@ func (s *Server) Rollback(ctx context.Context, rollbackReq *application.Applicat
 	return a, nil
 }
 
+func (s *Server) ListLinks(ctx context.Context, req *application.ListAppLinksRequest) (*application.LinksResponse, error) {
+	appName := req.GetName()
+	appNs := s.appNamespaceOrDefault(req.GetNamespace())
+
+	a, err := s.appclientset.ArgoprojV1alpha1().Applications(appNs).Get(ctx, appName, metav1.GetOptions{})
+	if err != nil {
+		log.WithFields(map[string]interface{}{
+			"application": appName,
+			"ns":          appNs,
+		}).Errorf("failed to get application, error=%v", err.Error())
+		return nil, fmt.Errorf("error getting application")
+	}
+
+	if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceApplications, rbacpolicy.ActionGet, a.RBACName(s.ns)); err != nil {
+		log.WithFields(map[string]interface{}{
+			"application": appName,
+			"ns":          appNs,
+		}).Warnf("unauthorized access to app, error=%v", err.Error())
+		return nil, fmt.Errorf("error getting application")
+	}
+
+	obj, err := kube.ToUnstructured(a)
+	if err != nil {
+		return nil, fmt.Errorf("error getting application: %w", err)
+	}
+
+	deepLinks, err := s.settingsMgr.GetDeepLinks(settings.ApplicationDeepLinks)
+	if err != nil {
+		return nil, fmt.Errorf("failed to read application deep links from configmap: %w", err)
+	}
+
+	finalList, errorList := deeplinks.EvaluateDeepLinksResponse(*obj, deepLinks)
+	if len(errorList) > 0 {
+		log.Errorf("errorList while evaluating application deep links, %v", strings.Join(errorList, ", "))
+	}
+
+	return finalList, nil
+}
+
+func (s *Server) ListResourceLinks(ctx context.Context, req *application.ApplicationResourceRequest) (*application.LinksResponse, error) {
+	obj, _, _, _, err := s.getUnstructuredLiveResourceOrApp(ctx, rbacpolicy.ActionGet, req)
+	if err != nil {
+		return nil, err
+	}
+
+	deepLinks, err := s.settingsMgr.GetDeepLinks(settings.ResourceDeepLinks)
+	if err != nil {
+		return nil, fmt.Errorf("failed to read application deep links from configmap: %w", err)
+	}
+
+	finalList, errorList := deeplinks.EvaluateDeepLinksResponse(*obj, deepLinks)
+	if len(errorList) > 0 {
+		log.Errorf("errors while evaluating resource deep links, %v", strings.Join(errorList, ", "))
+	}
+
+	return finalList, nil
+}
+
 // resolveRevision resolves the revision specified either in the sync request, or the
 // application source, into a concrete revision that will be used for a sync operation.
 func (s *Server) resolveRevision(ctx context.Context, app *appv1.Application, syncReq *application.ApplicationSyncRequest) (string, string, error) {
diff --git a/server/application/application.proto b/server/application/application.proto
index 50c229015..4fb08a708 100644
--- a/server/application/application.proto
+++ b/server/application/application.proto
@@ -264,6 +264,23 @@ message ManagedResourcesResponse {
 	repeated github.com.argoproj.argo_cd.v2.pkg.apis.application.v1alpha1.ResourceDiff items = 1;
 }
 
+message LinkInfo {
+	required string title = 1;
+	required string url = 2;
+	optional string description = 3;
+	optional string iconClass = 4;
+}
+
+message LinksResponse {
+	repeated LinkInfo items = 1;
+}
+
+message ListAppLinksRequest {
+	required string name = 1;
+	optional string namespace = 3;
+}
+
+
 // ApplicationService
 service ApplicationService {
 
@@ -425,4 +442,14 @@ service ApplicationService {
 			}
 		};
 	}
+
+	// ListLinks returns the list of all application deep links
+	rpc ListLinks(ListAppLinksRequest) returns (LinksResponse) {
+		option (google.api.http).get = "/api/v1/applications/{name}/links";
+	}
+
+	// ListResourceLinks returns the list of all resource deep links
+	rpc ListResourceLinks(ApplicationResourceRequest) returns (LinksResponse) {
+		option (google.api.http).get = "/api/v1/applications/{name}/resource/links";
+	}
 }
diff --git a/server/deeplinks/deeplinks.go b/server/deeplinks/deeplinks.go
new file mode 100644
index 000000000..096521ab2
--- /dev/null
+++ b/server/deeplinks/deeplinks.go
@@ -0,0 +1,63 @@
+package deeplinks
+
+import (
+	"bytes"
+	"fmt"
+	"text/template"
+
+	"github.com/antonmedv/expr"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/utils/pointer"
+
+	"github.com/argoproj/argo-cd/v2/pkg/apiclient/application"
+	"github.com/argoproj/argo-cd/v2/util/settings"
+)
+
+func EvaluateDeepLinksResponse(obj unstructured.Unstructured, links []settings.DeepLink) (*application.LinksResponse, []string) {
+	finalLinks := []*application.LinkInfo{}
+	errors := []string{}
+	for _, link := range links {
+		t, err := template.New("deep-link").Parse(link.URL)
+		if err != nil {
+			errors = append(errors, fmt.Sprintf("failed to parse link template '%v', error=%v", link.URL, err.Error()))
+			continue
+		}
+		finalURL := bytes.Buffer{}
+		err = t.Execute(&finalURL, obj.Object)
+		if err != nil {
+			errors = append(errors, fmt.Sprintf("failed to evaluate link template '%v' with resource %v, error=%v", link.URL, obj.GetName(), err.Error()))
+			continue
+		}
+		if link.Condition != nil {
+			out, err := expr.Eval(*link.Condition, obj.Object)
+			if err != nil {
+				errors = append(errors, fmt.Sprintf("failed to evaluate link condition '%v' with resource %v, error=%v", *link.Condition, obj.GetName(), err.Error()))
+				continue
+			}
+			switch resOut := out.(type) {
+			case bool:
+				if resOut {
+					finalLinks = append(finalLinks, &application.LinkInfo{
+						Title:       pointer.String(link.Title),
+						Url:         pointer.String(finalURL.String()),
+						Description: link.Description,
+						IconClass:   link.IconClass,
+					})
+				}
+			default:
+				errors = append(errors, fmt.Sprintf("link condition '%v' evaluated to non-boolean value for resource %v", *link.Condition, obj.GetName()))
+				continue
+			}
+		} else {
+			finalLinks = append(finalLinks, &application.LinkInfo{
+				Title:       pointer.String(link.Title),
+				Url:         pointer.String(finalURL.String()),
+				Description: link.Description,
+				IconClass:   link.IconClass,
+			})
+		}
+	}
+	return &application.LinksResponse{
+		Items: finalLinks,
+	}, errors
+}
diff --git a/server/extension/extension.go b/server/extension/extension.go
new file mode 100644
index 000000000..0865026e5
--- /dev/null
+++ b/server/extension/extension.go
@@ -0,0 +1,357 @@
+package extension
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"net"
+	"net/http"
+	"net/http/httputil"
+	"net/url"
+	"regexp"
+	"strings"
+	"time"
+
+	applicationpkg "github.com/argoproj/argo-cd/v2/pkg/apiclient/application"
+	v1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
+	"github.com/argoproj/argo-cd/v2/util/settings"
+	"github.com/ghodss/yaml"
+	"github.com/gorilla/mux"
+	log "github.com/sirupsen/logrus"
+	"k8s.io/utils/pointer"
+)
+
+const (
+	URLPrefix                    = "/extensions"
+	HeaderArgoCDApplicationName  = "Argocd-Application-Name"
+	DefaultConnectionTimeout     = 2 * time.Second
+	DefaultKeepAlive             = 15 * time.Second
+	DefaultIdleConnectionTimeout = 60 * time.Second
+	DefaultMaxIdleConnections    = 30
+)
+
+// ExtensionConfigs defines the configurations for all extensions
+// retrieved from Argo CD configmap (argocd-cm).
+type ExtensionConfigs struct {
+	Extensions []ExtensionConfig `json:"extensions"`
+}
+
+// ExtensionConfig defines the configuration for one extension.
+type ExtensionConfig struct {
+	// Name defines the endpoint that will be used to register
+	// the extension route. Mandatory field.
+	Name    string        `json:"name"`
+	Backend BackendConfig `json:"backend"`
+}
+
+// BackendConfig defines the backend service configurations that will
+// be used by an specific extension. An extension can have multiple services
+// associated. This is necessary when Argo CD is managing applications in
+// external clusters. In this case, each cluster may have its own backend
+// service.
+type BackendConfig struct {
+	ProxyConfig
+	Services []ServiceConfig `json:"services"`
+}
+
+// ProxyConfig allows configuring connection behaviour between Argo CD
+// API Server and the backend service.
+type ProxyConfig struct {
+	// ConnectionTimeout is the maximum amount of time a dial to
+	// the extension server will wait for a connect to complete.
+	// Default: 2 seconds
+	ConnectionTimeout time.Duration `json:"connectionTimeout"`
+
+	// KeepAlive specifies the interval between keep-alive probes
+	// for an active network connection between the API server and
+	// the extension server.
+	// Default: 15 seconds
+	KeepAlive time.Duration `json:"keepAlive"`
+
+	// IdleConnectionTimeout is the maximum amount of time an idle
+	// (keep-alive) connection between the API server and the extension
+	// server will remain idle before closing itself.
+	// Default: 60 seconds
+	IdleConnectionTimeout time.Duration `json:"idleConnectionTimeout"`
+
+	// MaxIdleConnections controls the maximum number of idle (keep-alive)
+	// connections between the API server and the extension server.
+	// Default: 30
+	MaxIdleConnections int `json:"maxIdleConnections"`
+}
+
+// ServiceConfig provides the configuration for a backend service.
+type ServiceConfig struct {
+	// URL is the address where the extension backend must be available.
+	// Mandatory field.
+	URL string `json:"url"`
+
+	// Cluster if provided, will have to match the application
+	// destination name to have requests properly forwarded to this
+	// service URL.
+	Cluster string `json:"cluster"`
+}
+
+// SettingsGetter defines the contract to retrieve Argo CD Settings.
+type SettingsGetter interface {
+	Get() (*settings.ArgoCDSettings, error)
+}
+
+// DefaultSettingsGetter is the real settings getter implementation.
+type DefaultSettingsGetter struct {
+	settingsMgr *settings.SettingsManager
+}
+
+// NewDefaultSettingsGetter returns a new default settings getter.
+func NewDefaultSettingsGetter(mgr *settings.SettingsManager) *DefaultSettingsGetter {
+	return &DefaultSettingsGetter{
+		settingsMgr: mgr,
+	}
+}
+
+// Get will retrieve the Argo CD settings.
+func (s *DefaultSettingsGetter) Get() (*settings.ArgoCDSettings, error) {
+	return s.settingsMgr.GetSettings()
+}
+
+// ApplicationGetter defines the contract to retrieve the application resource.
+type ApplicationGetter interface {
+	Get(ns, name string) (*v1alpha1.Application, error)
+}
+
+// DefaultApplicationGetter is the real application getter implementation.
+type DefaultApplicationGetter struct {
+	svc applicationpkg.ApplicationServiceServer
+}
+
+// NewDefaultApplicationGetter returns the default application getter.
+func NewDefaultApplicationGetter(appSvc applicationpkg.ApplicationServiceServer) *DefaultApplicationGetter {
+	return &DefaultApplicationGetter{
+		svc: appSvc,
+	}
+}
+
+// Get will retrieve the application resorce for the given namespace and name.
+func (a *DefaultApplicationGetter) Get(ns, name string) (*v1alpha1.Application, error) {
+	query := &applicationpkg.ApplicationQuery{
+		Name:         pointer.String(name),
+		AppNamespace: pointer.String(ns),
+	}
+	return a.svc.Get(context.Background(), query)
+}
+
+// Manager is the object that will be responsible for registering
+// and handling proxy extensions.
+type Manager struct {
+	log         *log.Entry
+	settings    SettingsGetter
+	application ApplicationGetter
+}
+
+// NewManager will initialize a new manager.
+func NewManager(sg SettingsGetter, ag ApplicationGetter, log *log.Entry) *Manager {
+	return &Manager{
+		log:         log,
+		settings:    sg,
+		application: ag,
+	}
+}
+
+func parseAndValidateConfig(config string) (*ExtensionConfigs, error) {
+	configs := ExtensionConfigs{}
+	err := yaml.Unmarshal([]byte(config), &configs)
+	if err != nil {
+		return nil, fmt.Errorf("invalid yaml: %s", err)
+	}
+	err = validateConfigs(&configs)
+	if err != nil {
+		return nil, fmt.Errorf("validation error: %s", err)
+	}
+	return &configs, nil
+}
+
+func validateConfigs(configs *ExtensionConfigs) error {
+	nameSafeRegex := regexp.MustCompile(`^[A-Za-z0-9-_]+$`)
+	for _, ext := range configs.Extensions {
+		if ext.Name == "" {
+			return fmt.Errorf("extensions.name must be configured")
+		}
+		if !nameSafeRegex.MatchString(ext.Name) {
+			return fmt.Errorf("invalid extensions.name: only alphanumeric characters, hyphens, and underscores are allowed")
+		}
+		svcTotal := len(ext.Backend.Services)
+		for _, svc := range ext.Backend.Services {
+			if svc.URL == "" {
+				return fmt.Errorf("extensions.backend.services.url must be configured")
+			}
+			if svcTotal > 1 && svc.Cluster == "" {
+				return fmt.Errorf("extensions.backend.services.cluster must be configured when defining more than one service per extension")
+			}
+		}
+	}
+	return nil
+}
+
+// NewProxy will instantiate a new reverse proxy based on the provided
+// targetURL and config.
+func NewProxy(targetURL string, config ProxyConfig) (*httputil.ReverseProxy, error) {
+	url, err := url.Parse(targetURL)
+	if err != nil {
+		return nil, fmt.Errorf("failed to parse proxy URL: %s", err)
+	}
+	proxy := httputil.NewSingleHostReverseProxy(url)
+	proxy.Transport = newTransport(config)
+	return proxy, nil
+}
+
+// newTransport will build a new transport to be used in the proxy
+// applying default values if not defined in the given config.
+func newTransport(config ProxyConfig) *http.Transport {
+	applyProxyConfigDefaults(&config)
+	return &http.Transport{
+		DialContext: (&net.Dialer{
+			Timeout:   config.ConnectionTimeout,
+			KeepAlive: config.KeepAlive,
+		}).DialContext,
+		MaxIdleConns:          config.MaxIdleConnections,
+		IdleConnTimeout:       config.IdleConnectionTimeout,
+		TLSHandshakeTimeout:   10 * time.Second,
+		ExpectContinueTimeout: 1 * time.Second,
+	}
+}
+
+func applyProxyConfigDefaults(c *ProxyConfig) {
+	if c.ConnectionTimeout == 0 {
+		c.ConnectionTimeout = DefaultConnectionTimeout
+	}
+	if c.KeepAlive == 0 {
+		c.KeepAlive = DefaultKeepAlive
+	}
+	if c.IdleConnectionTimeout == 0 {
+		c.IdleConnectionTimeout = DefaultIdleConnectionTimeout
+	}
+	if c.MaxIdleConnections == 0 {
+		c.MaxIdleConnections = DefaultMaxIdleConnections
+	}
+}
+
+// RegisterHandlers will retrieve all configured extensions
+// and register the respective http handlers in the given
+// router.
+func (m *Manager) RegisterHandlers(r *mux.Router) error {
+	m.log.Info("Registering extension handlers...")
+	config, err := m.settings.Get()
+	if err != nil {
+		return fmt.Errorf("error getting settings: %s", err)
+	}
+
+	if config.ExtensionConfig == "" {
+		return fmt.Errorf("No extensions configurations found")
+	}
+
+	extConfigs, err := parseAndValidateConfig(config.ExtensionConfig)
+	if err != nil {
+		return fmt.Errorf("error parsing extension config: %s", err)
+	}
+	return m.registerExtensions(r, extConfigs)
+}
+
+// registerExtensions will iterate over the given extConfigs and register
+// http handlers for every extension. It also registers a list extensions
+// handler under the "/extensions/" endpoint.
+func (m *Manager) registerExtensions(r *mux.Router, extConfigs *ExtensionConfigs) error {
+	extRouter := r.PathPrefix(fmt.Sprintf("%s/", URLPrefix)).Subrouter()
+	for _, ext := range extConfigs.Extensions {
+		proxyByCluster := make(map[string]*httputil.ReverseProxy)
+		for _, service := range ext.Backend.Services {
+			proxy, err := NewProxy(service.URL, ext.Backend.ProxyConfig)
+			if err != nil {
+				return fmt.Errorf("error creating proxy: %s", err)
+			}
+			proxyByCluster[service.Cluster] = proxy
+		}
+		m.log.Infof("Registering handler for %s/%s...", URLPrefix, ext.Name)
+		extRouter.PathPrefix(fmt.Sprintf("/%s/", ext.Name)).
+			HandlerFunc(m.CallExtension(ext.Name, proxyByCluster))
+	}
+	return nil
+}
+
+// CallExtension returns a handler func responsible for forwarding requests to the
+// extension service. The request will be sanitized by removing sensitive headers.
+func (m *Manager) CallExtension(extName string, proxyByCluster map[string]*httputil.ReverseProxy) func(http.ResponseWriter, *http.Request) {
+	return func(w http.ResponseWriter, r *http.Request) {
+		sanitizeRequest(r, extName)
+		if len(proxyByCluster) == 1 {
+			for _, proxy := range proxyByCluster {
+				proxy.ServeHTTP(w, r)
+				return
+			}
+		}
+		appHeader := r.Header.Get(HeaderArgoCDApplicationName)
+		if appHeader == "" {
+			msg := fmt.Sprintf("Header %q must be provided", HeaderArgoCDApplicationName)
+			m.writeErrorResponse(http.StatusBadRequest, msg, w)
+			return
+		}
+		appNamespace, appName, err := getAppName(appHeader)
+		if err != nil {
+			msg := fmt.Sprintf("Error getting application name: %s", err)
+			m.writeErrorResponse(http.StatusBadRequest, msg, w)
+			return
+		}
+		app, err := m.application.Get(appNamespace, appName)
+		if err != nil {
+			msg := fmt.Sprintf("Error getting application: %s", err)
+			m.writeErrorResponse(http.StatusBadRequest, msg, w)
+			return
+		}
+		if app == nil {
+			msg := fmt.Sprintf("Invalid Application: %s", appHeader)
+			m.writeErrorResponse(http.StatusBadRequest, msg, w)
+			return
+		}
+		clusterName := app.Spec.Destination.Name
+		if clusterName == "" {
+			clusterName = app.Spec.Destination.Server
+		}
+
+		proxy, ok := proxyByCluster[clusterName]
+		if !ok {
+			msg := fmt.Sprintf("No extension configured for cluster %q", clusterName)
+			m.writeErrorResponse(http.StatusBadRequest, msg, w)
+			return
+		}
+		proxy.ServeHTTP(w, r)
+	}
+}
+
+func getAppName(appHeader string) (string, string, error) {
+	parts := strings.Split(appHeader, "/")
+	if len(parts) != 2 {
+		return "", "", fmt.Errorf("invalid header value %q: expected format: <namespace>/<app-name>", appHeader)
+	}
+	return parts[0], parts[1], nil
+}
+
+func sanitizeRequest(r *http.Request, extName string) {
+	r.URL.Path = strings.TrimPrefix(r.URL.String(), fmt.Sprintf("%s/%s", URLPrefix, extName))
+}
+
+func (m *Manager) writeErrorResponse(status int, message string, w http.ResponseWriter) {
+	w.WriteHeader(status)
+	w.Header().Set("Content-Type", "application/json")
+	resp := make(map[string]string)
+	resp["status"] = http.StatusText(status)
+	resp["message"] = message
+	jsonResp, err := json.Marshal(resp)
+	if err != nil {
+		m.log.Errorf("Error marshaling response for extension: %s", err)
+		return
+	}
+	_, err = w.Write(jsonResp)
+	if err != nil {
+		m.log.Errorf("Error writing response for extension: %s", err)
+		return
+	}
+}
diff --git a/server/extension/mocks/ApplicationGetter.go b/server/extension/mocks/ApplicationGetter.go
new file mode 100644
index 000000000..bad203f47
--- /dev/null
+++ b/server/extension/mocks/ApplicationGetter.go
@@ -0,0 +1,51 @@
+// Code generated by mockery v2.15.0. DO NOT EDIT.
+
+package mocks
+
+import (
+	v1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
+	mock "github.com/stretchr/testify/mock"
+)
+
+// ApplicationGetter is an autogenerated mock type for the ApplicationGetter type
+type ApplicationGetter struct {
+	mock.Mock
+}
+
+// Get provides a mock function with given fields: ns, name
+func (_m *ApplicationGetter) Get(ns string, name string) (*v1alpha1.Application, error) {
+	ret := _m.Called(ns, name)
+
+	var r0 *v1alpha1.Application
+	if rf, ok := ret.Get(0).(func(string, string) *v1alpha1.Application); ok {
+		r0 = rf(ns, name)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*v1alpha1.Application)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(string, string) error); ok {
+		r1 = rf(ns, name)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+type mockConstructorTestingTNewApplicationGetter interface {
+	mock.TestingT
+	Cleanup(func())
+}
+
+// NewApplicationGetter creates a new instance of ApplicationGetter. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
+func NewApplicationGetter(t mockConstructorTestingTNewApplicationGetter) *ApplicationGetter {
+	mock := &ApplicationGetter{}
+	mock.Mock.Test(t)
+
+	t.Cleanup(func() { mock.AssertExpectations(t) })
+
+	return mock
+}
diff --git a/server/extension/mocks/SettingsGetter.go b/server/extension/mocks/SettingsGetter.go
new file mode 100644
index 000000000..303de9c5e
--- /dev/null
+++ b/server/extension/mocks/SettingsGetter.go
@@ -0,0 +1,51 @@
+// Code generated by mockery v2.15.0. DO NOT EDIT.
+
+package mocks
+
+import (
+	settings "github.com/argoproj/argo-cd/v2/util/settings"
+	mock "github.com/stretchr/testify/mock"
+)
+
+// SettingsGetter is an autogenerated mock type for the SettingsGetter type
+type SettingsGetter struct {
+	mock.Mock
+}
+
+// Get provides a mock function with given fields:
+func (_m *SettingsGetter) Get() (*settings.ArgoCDSettings, error) {
+	ret := _m.Called()
+
+	var r0 *settings.ArgoCDSettings
+	if rf, ok := ret.Get(0).(func() *settings.ArgoCDSettings); ok {
+		r0 = rf()
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*settings.ArgoCDSettings)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func() error); ok {
+		r1 = rf()
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+type mockConstructorTestingTNewSettingsGetter interface {
+	mock.TestingT
+	Cleanup(func())
+}
+
+// NewSettingsGetter creates a new instance of SettingsGetter. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
+func NewSettingsGetter(t mockConstructorTestingTNewSettingsGetter) *SettingsGetter {
+	mock := &SettingsGetter{}
+	mock.Mock.Test(t)
+
+	t.Cleanup(func() { mock.AssertExpectations(t) })
+
+	return mock
+}
diff --git a/server/project/project.go b/server/project/project.go
index d077b3bbc..9c86cf1ea 100644
--- a/server/project/project.go
+++ b/server/project/project.go
@@ -6,8 +6,7 @@ import (
 	"reflect"
 	"strings"
 
-	"github.com/argoproj/argo-cd/v2/util/db"
-
+	"github.com/argoproj/gitops-engine/pkg/utils/kube"
 	"github.com/argoproj/pkg/sync"
 	"github.com/golang-jwt/jwt/v4"
 	"github.com/google/uuid"
@@ -22,12 +21,15 @@ import (
 	"k8s.io/client-go/tools/cache"
 	"k8s.io/client-go/util/retry"
 
+	"github.com/argoproj/argo-cd/v2/pkg/apiclient/application"
 	"github.com/argoproj/argo-cd/v2/pkg/apiclient/project"
 	"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
 	appclientset "github.com/argoproj/argo-cd/v2/pkg/client/clientset/versioned"
 	listersv1alpha1 "github.com/argoproj/argo-cd/v2/pkg/client/listers/application/v1alpha1"
+	"github.com/argoproj/argo-cd/v2/server/deeplinks"
 	"github.com/argoproj/argo-cd/v2/server/rbacpolicy"
 	"github.com/argoproj/argo-cd/v2/util/argo"
+	"github.com/argoproj/argo-cd/v2/util/db"
 	jwtutil "github.com/argoproj/argo-cd/v2/util/jwt"
 	"github.com/argoproj/argo-cd/v2/util/rbac"
 	"github.com/argoproj/argo-cd/v2/util/session"
@@ -155,6 +157,39 @@ func (s *Server) createToken(ctx context.Context, q *project.ProjectTokenCreateR
 
 }
 
+func (s *Server) ListLinks(ctx context.Context, q *project.ListProjectLinksRequest) (*application.LinksResponse, error) {
+	projName := q.GetName()
+
+	if err := s.enf.EnforceErr(ctx.Value("claims"), rbacpolicy.ResourceProjects, rbacpolicy.ActionGet, projName); err != nil {
+		log.WithFields(map[string]interface{}{
+			"project": projName,
+		}).Warnf("unauthorized access to project, error=%v", err.Error())
+		return nil, fmt.Errorf("unauthorized access to project %v", projName)
+	}
+
+	proj, err := s.appclientset.ArgoprojV1alpha1().AppProjects(s.ns).Get(ctx, projName, metav1.GetOptions{})
+	if err != nil {
+		return nil, err
+	}
+
+	obj, err := kube.ToUnstructured(proj)
+	if err != nil {
+		return nil, fmt.Errorf("error getting application: %w", err)
+	}
+
+	deepLinks, err := s.settingsMgr.GetDeepLinks(settings.ProjectDeepLinks)
+	if err != nil {
+		return nil, fmt.Errorf("failed to read application deep links from configmap: %w", err)
+	}
+
+	finalList, errorList := deeplinks.EvaluateDeepLinksResponse(*obj, deepLinks)
+	if len(errorList) > 0 {
+		log.Errorf("errorList while evaluating project deep links, %v", strings.Join(errorList, ", "))
+	}
+
+	return finalList, nil
+}
+
 // DeleteToken deletes a token in a project
 func (s *Server) DeleteToken(ctx context.Context, q *project.ProjectTokenDeleteRequest) (*project.EmptyResponse, error) {
 	var resp *project.EmptyResponse
diff --git a/server/project/project.proto b/server/project/project.proto
index 7d175000f..5cf4bc977 100644
--- a/server/project/project.proto
+++ b/server/project/project.proto
@@ -9,7 +9,7 @@ package project;
 import "google/api/annotations.proto";
 import "k8s.io/api/core/v1/generated.proto";
 import "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1/generated.proto";
-
+import "github.com/argoproj/argo-cd/v2/server/application/application.proto";
 
 // ProjectCreateRequest defines project creation parameters.
 message ProjectCreateRequest {
@@ -70,6 +70,10 @@ message DetailedProjectsResponse {
     repeated github.com.argoproj.argo_cd.v2.pkg.apis.application.v1alpha1.Cluster clusters = 4;
 }
 
+message ListProjectLinksRequest {
+  string name = 1;
+}
+
 // ProjectService
 service ProjectService {
 
@@ -136,4 +140,10 @@ service ProjectService {
   rpc GetSyncWindowsState(SyncWindowsQuery) returns (SyncWindowsResponse) {
       option (google.api.http).get = "/api/v1/projects/{name}/syncwindows";
   }
+
+  // ListLinks returns all deep links for the particular project
+  rpc ListLinks(ListProjectLinksRequest) returns (application.LinksResponse) {
+    option (google.api.http).get = "/api/v1/projects/{name}/links";
+  }
+
 }
\ No newline at end of file
diff --git a/server/server.go b/server/server.go
index 8336348c8..ec257a051 100644
--- a/server/server.go
+++ b/server/server.go
@@ -30,6 +30,7 @@ import (
 	"github.com/go-redis/redis/v8"
 	"github.com/golang-jwt/jwt/v4"
 	"github.com/gorilla/handlers"
+	gmux "github.com/gorilla/mux"
 	grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
 	grpc_auth "github.com/grpc-ecosystem/go-grpc-middleware/auth"
 	grpc_logrus "github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus"
@@ -86,6 +87,7 @@ import (
 	servercache "github.com/argoproj/argo-cd/v2/server/cache"
 	"github.com/argoproj/argo-cd/v2/server/certificate"
 	"github.com/argoproj/argo-cd/v2/server/cluster"
+	"github.com/argoproj/argo-cd/v2/server/extension"
 	"github.com/argoproj/argo-cd/v2/server/gpgkey"
 	"github.com/argoproj/argo-cd/v2/server/logout"
 	"github.com/argoproj/argo-cd/v2/server/metrics"
@@ -192,6 +194,7 @@ type ArgoCDServer struct {
 	apiFactory        api.Factory
 	secretInformer    cache.SharedIndexInformer
 	configMapInformer cache.SharedIndexInformer
+	serviceSet        *ArgoCDServiceSet
 }
 
 type ArgoCDServerOpts struct {
@@ -216,6 +219,7 @@ type ArgoCDServerOpts struct {
 	ContentSecurityPolicy string
 	ListenHost            string
 	ApplicationNamespaces []string
+	EnableProxyExtension  bool
 }
 
 // initializeDefaultProject creates the default project if it does not already exist
@@ -420,7 +424,8 @@ func (a *ArgoCDServer) Init(ctx context.Context) {
 // golang/protobuf).
 func (a *ArgoCDServer) Run(ctx context.Context, listeners *Listeners) {
 	a.userStateStorage.Init(ctx)
-
+	svcSet := newArgoCDServiceSet(a)
+	a.serviceSet = svcSet
 	grpcS, appResourceTreeFn := a.newGRPCServer()
 	grpcWebS := grpcweb.WrapServer(grpcS)
 	var httpS *http.Server
@@ -457,6 +462,7 @@ func (a *ArgoCDServer) Run(ctx context.Context, listeners *Listeners) {
 	if !a.useTLS() {
 		httpL = tcpm.Match(cmux.HTTP1Fast())
 		grpcL = tcpm.MatchWithWriters(cmux.HTTP2MatchHeaderFieldSendSettings("content-type", "application/grpc"))
+
 	} else {
 		// We first match on HTTP 1.1 methods.
 		httpL = tcpm.Match(cmux.HTTP1Fast())
@@ -705,6 +711,45 @@ func (a *ArgoCDServer) newGRPCServer() (*grpc.Server, application.AppResourceTre
 		grpc_util.PanicLoggerUnaryServerInterceptor(a.log),
 	)))
 	grpcS := grpc.NewServer(sOpts...)
+
+	versionpkg.RegisterVersionServiceServer(grpcS, a.serviceSet.VersionService)
+	clusterpkg.RegisterClusterServiceServer(grpcS, a.serviceSet.ClusterService)
+	applicationpkg.RegisterApplicationServiceServer(grpcS, a.serviceSet.ApplicationService)
+	applicationsetpkg.RegisterApplicationSetServiceServer(grpcS, a.serviceSet.ApplicationSetService)
+	notificationpkg.RegisterNotificationServiceServer(grpcS, a.serviceSet.NotificationService)
+	repositorypkg.RegisterRepositoryServiceServer(grpcS, a.serviceSet.RepoService)
+	repocredspkg.RegisterRepoCredsServiceServer(grpcS, a.serviceSet.RepoCredsService)
+	sessionpkg.RegisterSessionServiceServer(grpcS, a.serviceSet.SessionService)
+	settingspkg.RegisterSettingsServiceServer(grpcS, a.serviceSet.SettingsService)
+	projectpkg.RegisterProjectServiceServer(grpcS, a.serviceSet.ProjectService)
+	accountpkg.RegisterAccountServiceServer(grpcS, a.serviceSet.AccountService)
+	certificatepkg.RegisterCertificateServiceServer(grpcS, a.serviceSet.CertificateService)
+	gpgkeypkg.RegisterGPGKeyServiceServer(grpcS, a.serviceSet.GpgkeyService)
+	// Register reflection service on gRPC server.
+	reflection.Register(grpcS)
+	grpc_prometheus.Register(grpcS)
+	errorsutil.CheckError(a.serviceSet.ProjectService.NormalizeProjs())
+	return grpcS, a.serviceSet.AppResourceTreeFn
+}
+
+type ArgoCDServiceSet struct {
+	ClusterService        *cluster.Server
+	RepoService           *repository.Server
+	RepoCredsService      *repocreds.Server
+	SessionService        *session.Server
+	ApplicationService    applicationpkg.ApplicationServiceServer
+	AppResourceTreeFn     application.AppResourceTreeFn
+	ApplicationSetService applicationsetpkg.ApplicationSetServiceServer
+	ProjectService        *project.Server
+	SettingsService       *settings.Server
+	AccountService        *account.Server
+	NotificationService   notificationpkg.NotificationServiceServer
+	CertificateService    *certificate.Server
+	GpgkeyService         *gpgkey.Server
+	VersionService        *version.Server
+}
+
+func newArgoCDServiceSet(a *ArgoCDServer) *ArgoCDServiceSet {
 	kubectl := kubeutil.NewKubectl()
 	clusterService := cluster.NewServer(a.db, a.enf, a.Cache, kubectl)
 	repoService := repository.NewServer(a.RepoClientset, a.db, a.enf, a.Cache, a.appLister, a.projLister, a.settingsMgr)
@@ -740,7 +785,7 @@ func (a *ArgoCDServer) newGRPCServer() (*grpc.Server, application.AppResourceTre
 	notificationService := notification.NewServer(a.apiFactory)
 	certificateService := certificate.NewServer(a.RepoClientset, a.db, a.enf)
 	gpgkeyService := gpgkey.NewServer(a.RepoClientset, a.db, a.enf)
-	versionpkg.RegisterVersionServiceServer(grpcS, version.NewServer(a, func() (bool, error) {
+	versionService := version.NewServer(a, func() (bool, error) {
 		if a.DisableAuth {
 			return true, nil
 		}
@@ -749,24 +794,24 @@ func (a *ArgoCDServer) newGRPCServer() (*grpc.Server, application.AppResourceTre
 			return false, err
 		}
 		return sett.AnonymousUserEnabled, err
-	}))
-	clusterpkg.RegisterClusterServiceServer(grpcS, clusterService)
-	applicationpkg.RegisterApplicationServiceServer(grpcS, applicationService)
-	applicationsetpkg.RegisterApplicationSetServiceServer(grpcS, applicationSetService)
-	notificationpkg.RegisterNotificationServiceServer(grpcS, notificationService)
-	repositorypkg.RegisterRepositoryServiceServer(grpcS, repoService)
-	repocredspkg.RegisterRepoCredsServiceServer(grpcS, repoCredsService)
-	sessionpkg.RegisterSessionServiceServer(grpcS, sessionService)
-	settingspkg.RegisterSettingsServiceServer(grpcS, settingsService)
-	projectpkg.RegisterProjectServiceServer(grpcS, projectService)
-	accountpkg.RegisterAccountServiceServer(grpcS, accountService)
-	certificatepkg.RegisterCertificateServiceServer(grpcS, certificateService)
-	gpgkeypkg.RegisterGPGKeyServiceServer(grpcS, gpgkeyService)
-	// Register reflection service on gRPC server.
-	reflection.Register(grpcS)
-	grpc_prometheus.Register(grpcS)
-	errorsutil.CheckError(projectService.NormalizeProjs())
-	return grpcS, appResourceTreeFn
+	})
+
+	return &ArgoCDServiceSet{
+		ClusterService:        clusterService,
+		RepoService:           repoService,
+		RepoCredsService:      repoCredsService,
+		SessionService:        sessionService,
+		ApplicationService:    applicationService,
+		AppResourceTreeFn:     appResourceTreeFn,
+		ApplicationSetService: applicationSetService,
+		ProjectService:        projectService,
+		SettingsService:       settingsService,
+		AccountService:        accountService,
+		NotificationService:   notificationService,
+		CertificateService:    certificateService,
+		GpgkeyService:         gpgkeyService,
+		VersionService:        versionService,
+	}
 }
 
 // translateGrpcCookieHeader conditionally sets a cookie on the response.
@@ -896,6 +941,15 @@ func (a *ArgoCDServer) newHTTPServer(ctx context.Context, port int, grpcWebHandl
 		terminalHandler.ServeHTTP(writer, request)
 	})
 
+	// Dead code for now
+	// Proxy extension is currently an experimental feature and is disabled
+	// by default.
+	// if a.EnableProxyExtension {
+	// // API server won't panic if extensions fail to register. In
+	// // this case an error log will be sent and no extension route
+	// // will be added in mux.
+	// registerExtensions(mux, a)
+	// }
 	mustRegisterGWHandler(versionpkg.RegisterVersionServiceHandler, ctx, gwmux, conn)
 	mustRegisterGWHandler(clusterpkg.RegisterClusterServiceHandler, ctx, gwmux, conn)
 	mustRegisterGWHandler(applicationpkg.RegisterApplicationServiceHandler, ctx, gwmux, conn)
@@ -941,6 +995,24 @@ func (a *ArgoCDServer) newHTTPServer(ctx context.Context, port int, grpcWebHandl
 	return &httpS
 }
 
+// registerExtensions will try to register all configured extensions
+// in the given mux. If any error is returned while registering
+// extensions handlers, no route will be added in the given mux.
+// nolint:deadcode,unused,staticcheck
+func registerExtensions(mux *http.ServeMux, a *ArgoCDServer) {
+	sg := extension.NewDefaultSettingsGetter(a.settingsMgr)
+	ag := extension.NewDefaultApplicationGetter(a.serviceSet.ApplicationService)
+	em := extension.NewManager(sg, ag, a.log)
+	r := gmux.NewRouter()
+
+	err := em.RegisterHandlers(r)
+	if err != nil {
+		a.log.Errorf("error registering extension handlers: %s", err)
+		return
+	}
+	mux.Handle(fmt.Sprintf("%s/", extension.URLPrefix), r)
+}
+
 var extensionsPattern = regexp.MustCompile(`^extension(.*)\.js$`)
 
 func (a *ArgoCDServer) serveExtensions(extensionsSharedPath string, w http.ResponseWriter) {
diff --git a/server/version/version.go b/server/version/version.go
index dfd880937..e899a7ab5 100644
--- a/server/version/version.go
+++ b/server/version/version.go
@@ -13,7 +13,7 @@ import (
 	sessionmgr "github.com/argoproj/argo-cd/v2/util/session"
 )
 
-type server struct {
+type Server struct {
 	kustomizeVersion string
 	helmVersion      string
 	jsonnetVersion   string
@@ -21,12 +21,12 @@ type server struct {
 	disableAuth      func() (bool, error)
 }
 
-func NewServer(authenticator settings.Authenticator, disableAuth func() (bool, error)) *server {
-	return &server{authenticator: authenticator, disableAuth: disableAuth}
+func NewServer(authenticator settings.Authenticator, disableAuth func() (bool, error)) *Server {
+	return &Server{authenticator: authenticator, disableAuth: disableAuth}
 }
 
 // Version returns the version of the API server
-func (s *server) Version(ctx context.Context, _ *empty.Empty) (*version.VersionMessage, error) {
+func (s *Server) Version(ctx context.Context, _ *empty.Empty) (*version.VersionMessage, error) {
 	vers := common.GetVersion()
 	disableAuth, err := s.disableAuth()
 	if err != nil {
@@ -71,7 +71,7 @@ func (s *server) Version(ctx context.Context, _ *empty.Empty) (*version.VersionM
 }
 
 // AuthFuncOverride allows the version to be returned without auth
-func (s *server) AuthFuncOverride(ctx context.Context, fullMethodName string) (context.Context, error) {
+func (s *Server) AuthFuncOverride(ctx context.Context, fullMethodName string) (context.Context, error) {
 	if s.authenticator != nil {
 		// this authenticates the user, but ignores any error, so that we have claims populated
 		ctx, _ = s.authenticator.Authenticate(ctx)
diff --git a/ui/package.json b/ui/package.json
index 542a61bdf..58a4c7458 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -37,7 +37,7 @@
     "react-helmet": "^5.2.0",
     "react-hot-loader": "^3.1.3",
     "react-moment": "^0.9.7",
-    "react-paginate": "^6.5.0",
+    "react-paginate": "^8.1.4",
     "react-router": "^4.3.1",
     "react-router-dom": "^4.2.2",
     "react-svg-piechart": "^2.4.2",
@@ -105,7 +105,7 @@
     "tslint": "^6.1.3",
     "tslint-config-prettier": "^1.18.0",
     "tslint-plugin-prettier": "^2.0.1",
-    "tslint-react": "^3.4.0",
+    "tslint-react": "^5.0.0",
     "typescript": "^4.0.3",
     "webpack": "^5.70.0",
     "webpack-cli": "^4.9.2",
diff --git a/ui/src/app/applications/components/application-deployment-history/application-deployment-history.tsx b/ui/src/app/applications/components/application-deployment-history/application-deployment-history.tsx
index 73542ca5b..b612c9ed9 100644
--- a/ui/src/app/applications/components/application-deployment-history/application-deployment-history.tsx
+++ b/ui/src/app/applications/components/application-deployment-history/application-deployment-history.tsx
@@ -32,13 +32,13 @@ export const ApplicationDeploymentHistory = ({
                 <div className='row application-deployment-history__item' key={info.deployedAt} onClick={() => selectDeployment(index)}>
                     <div className='columns small-3'>
                         <div>
-                            <i className='fa fa-clock' /> Deployed At:
+                            <i className='fa fa-clock' /> <span class='show-for-large'>Deployed At:</span>
                             <br />
                             <Timestamp date={info.deployedAt} />
                         </div>
                         <div>
                             <br />
-                            <i className='fa fa-hourglass-half' /> Time to deploy:
+                            <i className='fa fa-hourglass-half' /> <span class='show-for-large'>Time to deploy:</span>
                             <br />
                             {(info.deployStartedAt && <Duration durationMs={moment(info.deployedAt).diff(moment(info.deployStartedAt)) / 1000} />) || 'Unknown'}
                         </div>
diff --git a/ui/src/app/applications/components/application-details/application-details.scss b/ui/src/app/applications/components/application-details/application-details.scss
index 4ea04076f..9f7433d2f 100644
--- a/ui/src/app/applications/components/application-details/application-details.scss
+++ b/ui/src/app/applications/components/application-details/application-details.scss
@@ -274,3 +274,74 @@ $header: 120px;
         }
     }
 }
+
+
+@media screen and (max-width: map-get($breakpoints, large)) {
+.sliding-panel__body {
+    padding: 4px !important;
+}
+.sliding-panel--is-middle .sliding-panel__wrapper {
+    width: 90% !important;
+}
+.sliding-panel--is-middle .sliding-panel__body {
+    padding: 18px !important;
+}
+.sliding-panel__close {
+    z-index: 2 !important;
+}
+.top-bar__title {
+    display: none;
+}
+
+.top-bar__left-side {
+    white-space: normal !important;
+}
+.top-bar__left-side > div {
+    display: block !important;
+}
+.top-bar__right-side {
+    justify-content: right !important;
+}
+.application-status-panel.row {
+    flex-flow: unset;
+}
+.application-status-panel__item label {
+    margin-right: 0;
+}
+.application-status-panel__item {
+    padding: 5px 10px;
+}
+
+.white-box, .tabs__content {
+    padding: 4px !important;
+}
+.white-box__details-row .columns.small-3 {
+    overflow-wrap: unset !important;
+    overflow: scroll;
+}
+.white-box__details-row .columns.small-9{
+    padding-left: 4px;
+}
+
+.resource-details__header h1 {
+    font-size: 16px;
+}
+.resource-details__header {
+    margin-top: 30px;
+    padding-right: 4px;
+}
+
+.tabs__nav a:first-child, .tabs__nav a {
+    margin-left: 0 !important;
+}
+
+.editable-panel__buttons {
+    top: unset;
+}
+}
+
+@media screen and (max-width: map-get($breakpoints, medium)) {
+.sb-page-wrapper .top-bar.row {
+    display: none !important;
+}
+}
\ No newline at end of file
diff --git a/ui/src/app/applications/components/application-status-panel/application-status-panel.tsx b/ui/src/app/applications/components/application-status-panel/application-status-panel.tsx
index e8422c0f3..9bbb23370 100644
--- a/ui/src/app/applications/components/application-status-panel/application-status-panel.tsx
+++ b/ui/src/app/applications/components/application-status-panel/application-status-panel.tsx
@@ -88,7 +88,7 @@ export const ApplicationStatusPanel = ({application, showOperation, showConditio
                         <div>
                             <ComparisonStatusIcon status={application.status.sync.status} label={true} />
                         </div>
-                        <div className='application-status-panel__item-value__revision'>{syncStatusMessage(application)}</div>
+                        <div className='application-status-panel__item-value__revision show-for-large'>{syncStatusMessage(application)}</div>
                     </div>
                     {application.status && application.status.sync && application.status.sync.revision && !application.spec.source.chart && (
                         <div className='application-status-panel__item-name'>
@@ -121,7 +121,7 @@ export const ApplicationStatusPanel = ({application, showOperation, showConditio
                                 <OperationState app={application} />{' '}
                             </a>
                             {appOperationState.syncResult && appOperationState.syncResult.revision && (
-                                <div className='application-status-panel__item-value__revision'>
+                                <div className='application-status-panel__item-value__revision show-for-large'>
                                     To <Revision repoUrl={application.spec.source.repoURL} revision={appOperationState.syncResult.revision} />
                                 </div>
                             )}
diff --git a/ui/src/app/applications/components/application-summary/application-summary.tsx b/ui/src/app/applications/components/application-summary/application-summary.tsx
index 24f0bd9c3..4323f5df3 100644
--- a/ui/src/app/applications/components/application-summary/application-summary.tsx
+++ b/ui/src/app/applications/components/application-summary/application-summary.tsx
@@ -174,7 +174,7 @@ export const ApplicationSummary = (props: ApplicationSummaryProps) => {
                               load={src => services.repos.charts(src.repoURL).catch(() => new Array<models.HelmChart>())}>
                               {(charts: models.HelmChart[]) => (
                                   <div className='row'>
-                                      <div className='columns small-10'>
+                                      <div className='columns small-8'>
                                           <FormField
                                               formApi={formApi}
                                               field='spec.source.chart'
@@ -192,7 +192,7 @@ export const ApplicationSummary = (props: ApplicationSummaryProps) => {
                                               return (chartInfo && chartInfo.versions) || new Array<string>();
                                           }}>
                                           {(versions: string[]) => (
-                                              <div className='columns small-2'>
+                                              <div className='columns small-4'>
                                                   <FormField
                                                       formApi={formApi}
                                                       field='spec.source.targetRevision'
diff --git a/ui/src/app/applications/components/applications-list/applications-list.scss b/ui/src/app/applications/components/applications-list/applications-list.scss
index 5325f6387..fbe03245e 100644
--- a/ui/src/app/applications/components/applications-list/applications-list.scss
+++ b/ui/src/app/applications/components/applications-list/applications-list.scss
@@ -4,6 +4,9 @@
 
 .applications-list {
     padding: 1em;
+    @media screen and (max-width: 1024px) {
+        padding: 0;
+    }
     min-height: 88vh;
     &__title {
         font-weight: bolder;
@@ -208,3 +211,11 @@
 i.menu_icon {
     vertical-align: middle;
 }
+
+.argo-button {
+    i {
+        @media screen and (max-width: map-get($breakpoints, large)) {
+            margin: 0 auto !important;
+        }
+    }
+}
\ No newline at end of file
diff --git a/ui/src/app/applications/components/applications-list/applications-list.tsx b/ui/src/app/applications/components/applications-list/applications-list.tsx
index eff091e4c..9b5e66417 100644
--- a/ui/src/app/applications/components/applications-list/applications-list.tsx
+++ b/ui/src/app/applications/components/applications-list/applications-list.tsx
@@ -291,7 +291,7 @@ const FlexTopBar = (props: {toolbar: Toolbar | Observable<Toolbar>}) => {
                                                 style={{marginRight: 2}}
                                                 key={i}>
                                                 {item.iconClassName && <i className={item.iconClassName} style={{marginLeft: '-5px', marginRight: '5px'}} />}
-                                                {item.title}
+                                                <span className='show-for-large'>{item.title}</span>
                                             </button>
                                         ))}
                                     </React.Fragment>
diff --git a/ui/src/app/applications/components/resource-details/resource-details.tsx b/ui/src/app/applications/components/resource-details/resource-details.tsx
index 14ab321ff..7b677a8d3 100644
--- a/ui/src/app/applications/components/resource-details/resource-details.tsx
+++ b/ui/src/app/applications/components/resource-details/resource-details.tsx
@@ -307,13 +307,13 @@ export const ResourceDetails = (props: ResourceDetailsProps) => {
                                     onClick={() => appContext.navigation.goto('.', {deploy: AppUtils.nodeKey(selectedNode)}, {replace: true})}
                                     style={{marginLeft: 'auto', marginRight: '5px'}}
                                     className='argo-button argo-button--base'>
-                                    <i className='fa fa-sync-alt' /> SYNC
+                                    <i className='fa fa-sync-alt' /> <span className='show-for-large'>SYNC</span>
                                 </button>
                                 <button
                                     onClick={() => AppUtils.deletePopup(appContext, selectedNode, application)}
                                     style={{marginRight: '5px'}}
                                     className='argo-button argo-button--base'>
-                                    <i className='fa fa-trash' /> DELETE
+                                    <i className='fa fa-trash' /> <span className='show-for-large'>DELETE</span>
                                 </button>
                                 <DropDown
                                     isMenu={true}
diff --git a/ui/src/app/shared/components/timestamp.tsx b/ui/src/app/shared/components/timestamp.tsx
index 05cd45238..034d21c24 100644
--- a/ui/src/app/shared/components/timestamp.tsx
+++ b/ui/src/app/shared/components/timestamp.tsx
@@ -4,7 +4,11 @@ import Moment from 'react-moment';
 export const Timestamp = ({date}: {date: string | number}) => {
     return (
         <span>
-            <Moment fromNow={true}>{date}</Moment> (<Moment local={true}>{date}</Moment>)
+            <Moment fromNow={true}>{date}</Moment>
+            <span className='show-for-large'>
+                {' '}
+                (<Moment local={true}>{date}</Moment>)
+            </span>
         </span>
     );
 };
diff --git a/ui/yarn.lock b/ui/yarn.lock
index 6c3dca73f..73c937d23 100644
--- a/ui/yarn.lock
+++ b/ui/yarn.lock
@@ -2112,13 +2112,13 @@ abab@^2.0.3, abab@^2.0.5:
   resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a"
   integrity sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==
 
-accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7:
-  version "1.3.7"
-  resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd"
-  integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==
+accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.8:
+  version "1.3.8"
+  resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e"
+  integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==
   dependencies:
-    mime-types "~2.1.24"
-    negotiator "0.6.2"
+    mime-types "~2.1.34"
+    negotiator "0.6.3"
 
 acorn-globals@^6.0.0:
   version "6.0.0"
@@ -2426,7 +2426,7 @@ arr-union@^3.1.0:
 array-flatten@1.1.1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
-  integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=
+  integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==
 
 array-flatten@^2.1.0:
   version "2.1.2"
@@ -2641,21 +2641,23 @@ binary-extensions@^2.0.0:
   resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
   integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==
 
-body-parser@1.19.0:
-  version "1.19.0"
-  resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a"
-  integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==
+body-parser@1.20.1:
+  version "1.20.1"
+  resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668"
+  integrity sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==
   dependencies:
-    bytes "3.1.0"
+    bytes "3.1.2"
     content-type "~1.0.4"
     debug "2.6.9"
-    depd "~1.1.2"
-    http-errors "1.7.2"
+    depd "2.0.0"
+    destroy "1.2.0"
+    http-errors "2.0.0"
     iconv-lite "0.4.24"
-    on-finished "~2.3.0"
-    qs "6.7.0"
-    raw-body "2.4.0"
-    type-is "~1.6.17"
+    on-finished "2.4.1"
+    qs "6.11.0"
+    raw-body "2.5.1"
+    type-is "~1.6.18"
+    unpipe "1.0.0"
 
 bonjour@^3.5.0:
   version "3.5.0"
@@ -2774,10 +2776,10 @@ bytes@3.0.0:
   resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048"
   integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=
 
-bytes@3.1.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6"
-  integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==
+bytes@3.1.2:
+  version "3.1.2"
+  resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5"
+  integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==
 
 cacache@^15.0.5:
   version "15.2.0"
@@ -3164,12 +3166,12 @@ connect-history-api-fallback@^1.6.0:
   resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz#8b32089359308d111115d81cad3fceab888f97bc"
   integrity sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==
 
-content-disposition@0.5.3:
-  version "0.5.3"
-  resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd"
-  integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==
+content-disposition@0.5.4:
+  version "0.5.4"
+  resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe"
+  integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==
   dependencies:
-    safe-buffer "5.1.2"
+    safe-buffer "5.2.1"
 
 content-type@~1.0.4:
   version "1.0.4"
@@ -3186,12 +3188,12 @@ convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0:
 cookie-signature@1.0.6:
   version "1.0.6"
   resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
-  integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw=
+  integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==
 
-cookie@0.4.0:
-  version "0.4.0"
-  resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba"
-  integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==
+cookie@0.5.0:
+  version "0.5.0"
+  resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b"
+  integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==
 
 cookiejar@^2.1.3:
   version "2.1.3"
@@ -3391,12 +3393,12 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3:
   dependencies:
     ms "2.0.0"
 
-debug@4, debug@^4.1.0, debug@^4.1.1:
-  version "4.1.1"
-  resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791"
-  integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==
+debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.4:
+  version "4.3.4"
+  resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
+  integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
   dependencies:
-    ms "^2.1.1"
+    ms "2.1.2"
 
 debug@^3.1.1:
   version "3.2.7"
@@ -3405,13 +3407,6 @@ debug@^3.1.1:
   dependencies:
     ms "^2.1.1"
 
-debug@^4.3.4:
-  version "4.3.4"
-  resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
-  integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
-  dependencies:
-    ms "2.1.2"
-
 decimal.js@^10.2.1:
   version "10.3.1"
   resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.3.1.tgz#d8c3a444a9c6774ba60ca6ad7261c3a94fd5e783"
@@ -3524,15 +3519,20 @@ delayed-stream@~1.0.0:
   resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
   integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk=
 
+depd@2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df"
+  integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==
+
 depd@~1.1.2:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
   integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=
 
-destroy@~1.0.4:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80"
-  integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=
+destroy@1.2.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015"
+  integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==
 
 detect-newline@^3.0.0:
   version "3.1.0"
@@ -3679,7 +3679,7 @@ dot-case@^3.0.4:
 ee-first@1.1.1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
-  integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
+  integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==
 
 electron-to-chromium@^1.3.723:
   version "1.3.772"
@@ -3714,7 +3714,7 @@ emojis-list@^3.0.0:
 encodeurl@~1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
-  integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=
+  integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==
 
 end-of-stream@^1.1.0:
   version "1.4.1"
@@ -4047,7 +4047,7 @@ escape-carriage@^1.3.0:
 escape-html@~1.0.3:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
-  integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=
+  integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==
 
 escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
   version "1.0.5"
@@ -4117,7 +4117,7 @@ esutils@^2.0.2:
 etag@~1.8.1:
   version "1.8.1"
   resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
-  integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=
+  integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==
 
 eventemitter3@^4.0.0, eventemitter3@^4.0.7:
   version "4.0.7"
@@ -4191,37 +4191,38 @@ expect@^27.5.1:
     jest-message-util "^27.5.1"
 
 express@^4.17.1:
-  version "4.17.1"
-  resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134"
-  integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==
+  version "4.18.2"
+  resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59"
+  integrity sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==
   dependencies:
-    accepts "~1.3.7"
+    accepts "~1.3.8"
     array-flatten "1.1.1"
-    body-parser "1.19.0"
-    content-disposition "0.5.3"
+    body-parser "1.20.1"
+    content-disposition "0.5.4"
     content-type "~1.0.4"
-    cookie "0.4.0"
+    cookie "0.5.0"
     cookie-signature "1.0.6"
     debug "2.6.9"
-    depd "~1.1.2"
+    depd "2.0.0"
     encodeurl "~1.0.2"
     escape-html "~1.0.3"
     etag "~1.8.1"
-    finalhandler "~1.1.2"
+    finalhandler "1.2.0"
     fresh "0.5.2"
+    http-errors "2.0.0"
     merge-descriptors "1.0.1"
     methods "~1.1.2"
-    on-finished "~2.3.0"
+    on-finished "2.4.1"
     parseurl "~1.3.3"
     path-to-regexp "0.1.7"
-    proxy-addr "~2.0.5"
-    qs "6.7.0"
+    proxy-addr "~2.0.7"
+    qs "6.11.0"
     range-parser "~1.2.1"
-    safe-buffer "5.1.2"
-    send "0.17.1"
-    serve-static "1.14.1"
-    setprototypeof "1.1.1"
-    statuses "~1.5.0"
+    safe-buffer "5.2.1"
+    send "0.18.0"
+    serve-static "1.15.0"
+    setprototypeof "1.2.0"
+    statuses "2.0.1"
     type-is "~1.6.18"
     utils-merge "1.0.1"
     vary "~1.1.2"
@@ -4349,17 +4350,17 @@ filter-obj@^1.1.0:
   resolved "https://registry.yarnpkg.com/filter-obj/-/filter-obj-1.1.0.tgz#9b311112bc6c6127a16e016c6c5d7f19e0805c5b"
   integrity sha1-mzERErxsYSehbgFsbF1/GeCAXFs=
 
-finalhandler@~1.1.2:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d"
-  integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==
+finalhandler@1.2.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32"
+  integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==
   dependencies:
     debug "2.6.9"
     encodeurl "~1.0.2"
     escape-html "~1.0.3"
-    on-finished "~2.3.0"
+    on-finished "2.4.1"
     parseurl "~1.3.3"
-    statuses "~1.5.0"
+    statuses "2.0.1"
     unpipe "~1.0.0"
 
 find-cache-dir@^2.0.0:
@@ -4463,7 +4464,7 @@ fragment-cache@^0.2.1:
 fresh@0.5.2:
   version "0.5.2"
   resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
-  integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=
+  integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==
 
 fs-minipass@^2.0.0:
   version "2.1.0"
@@ -4510,13 +4511,13 @@ get-caller-file@^2.0.5:
   integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
 
 get-intrinsic@^1.0.2:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6"
-  integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==
+  version "1.1.3"
+  resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.3.tgz#063c84329ad93e83893c7f4f243ef63ffa351385"
+  integrity sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==
   dependencies:
     function-bind "^1.1.1"
     has "^1.0.3"
-    has-symbols "^1.0.1"
+    has-symbols "^1.0.3"
 
 get-package-type@^0.1.0:
   version "0.1.0"
@@ -4658,6 +4659,11 @@ has-symbols@^1.0.1, has-symbols@^1.0.2:
   resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423"
   integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==
 
+has-symbols@^1.0.3:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8"
+  integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==
+
 has-value@^0.3.1:
   version "0.3.1"
   resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f"
@@ -4796,16 +4802,16 @@ http-deceiver@^1.2.7:
   resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87"
   integrity sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=
 
-http-errors@1.7.2, http-errors@~1.7.2:
-  version "1.7.2"
-  resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f"
-  integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==
+http-errors@2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3"
+  integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==
   dependencies:
-    depd "~1.1.2"
-    inherits "2.0.3"
-    setprototypeof "1.1.1"
-    statuses ">= 1.5.0 < 2"
-    toidentifier "1.0.0"
+    depd "2.0.0"
+    inherits "2.0.4"
+    setprototypeof "1.2.0"
+    statuses "2.0.1"
+    toidentifier "1.0.1"
 
 http-errors@~1.6.2:
   version "1.6.3"
@@ -4963,7 +4969,7 @@ inflight@^1.0.4:
     once "^1.3.0"
     wrappy "1"
 
-inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3:
+inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3:
   version "2.0.4"
   resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
   integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
@@ -6160,7 +6166,7 @@ marked@^4.0.10:
 media-typer@0.3.0:
   version "0.3.0"
   resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
-  integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
+  integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==
 
 memfs@^3.4.1:
   version "3.4.1"
@@ -6177,7 +6183,7 @@ memoize-one@^6.0.0:
 merge-descriptors@1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
-  integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=
+  integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==
 
 merge-stream@^2.0.0:
   version "2.0.0"
@@ -6239,14 +6245,14 @@ mime-db@1.52.0:
   resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
   integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
 
-mime-types@^2.1.12, mime-types@~2.1.17, mime-types@~2.1.24:
+mime-types@^2.1.12, mime-types@~2.1.17:
   version "2.1.31"
   resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.31.tgz#a00d76b74317c61f9c2db2218b8e9f8e9c5c9e6b"
   integrity sha512-XGZnNzm3QvgKxa8dpzyhFTHmpP3l5YNusmne07VUOXxou9CqUqYa/HBy124RqtVh/O2pECas/MOcsDgpilPOPg==
   dependencies:
     mime-db "1.48.0"
 
-mime-types@^2.1.27, mime-types@^2.1.31:
+mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.24, mime-types@~2.1.34:
   version "2.1.35"
   resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
   integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
@@ -6394,18 +6400,18 @@ monaco-editor@^0.33.0:
 ms@2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
-  integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
-
-ms@2.1.1:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"
-  integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==
+  integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==
 
 ms@2.1.2, ms@^2.1.1:
   version "2.1.2"
   resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
   integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
 
+ms@2.1.3:
+  version "2.1.3"
+  resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
+  integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
+
 multicast-dns-service-types@^1.1.0:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz#899f11d9686e5e05cb91b35d5f0e63b773cfc901"
@@ -6446,10 +6452,10 @@ natural-compare@^1.4.0:
   resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
   integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=
 
-negotiator@0.6.2:
-  version "0.6.2"
-  resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"
-  integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==
+negotiator@0.6.3:
+  version "0.6.3"
+  resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd"
+  integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==
 
 neo-async@^2.6.2:
   version "2.6.2"
@@ -6619,9 +6625,9 @@ object-copy@^0.1.0:
     kind-of "^3.0.3"
 
 object-inspect@^1.9.0:
-  version "1.10.3"
-  resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.10.3.tgz#c2aa7d2d09f50c99375704f7a0adf24c5782d369"
-  integrity sha512-e5mCJlSH7poANfC8z8S9s9S2IN5/4Zb3aZ33f5s8YqoazCFzNLloLU8r5VCG+G7WoqLvAAZoVMcy3tp/3X0Plw==
+  version "1.12.2"
+  resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.2.tgz#c0641f26394532f28ab8d796ab954e43c009a8ea"
+  integrity sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==
 
 object-is@^1.0.1:
   version "1.1.5"
@@ -6665,10 +6671,10 @@ obuf@^1.0.0, obuf@^1.1.2:
   resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e"
   integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==
 
-on-finished@~2.3.0:
-  version "2.3.0"
-  resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"
-  integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=
+on-finished@2.4.1:
+  version "2.4.1"
+  resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f"
+  integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==
   dependencies:
     ee-first "1.1.1"
 
@@ -6880,7 +6886,7 @@ path-parse@^1.0.6, path-parse@^1.0.7:
 path-to-regexp@0.1.7:
   version "0.1.7"
   resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
-  integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=
+  integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==
 
 path-to-regexp@^1.7.0:
   version "1.8.0"
@@ -7145,16 +7151,7 @@ prompts@^2.0.1:
     kleur "^3.0.3"
     sisteransi "^1.0.5"
 
-prop-types@^15.5.0, prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.6.1, prop-types@^15.7.2:
-  version "15.7.2"
-  resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
-  integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
-  dependencies:
-    loose-envify "^1.4.0"
-    object-assign "^4.1.1"
-    react-is "^16.8.1"
-
-prop-types@^15.6.2, prop-types@^15.8.1:
+prop-types@^15, prop-types@^15.5.0, prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1:
   version "15.8.1"
   resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
   integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
@@ -7168,7 +7165,7 @@ protocols@^1.1.0, protocols@^1.4.0:
   resolved "https://registry.yarnpkg.com/protocols/-/protocols-1.4.8.tgz#48eea2d8f58d9644a4a32caae5d5db290a075ce8"
   integrity sha512-IgjKyaUSjsROSO8/D49Ab7hP8mJgTYcqApOqdPhLoPxAplXmkp+zRvsrSQjFn5by0rhm4VH0GAUELIPpx7B1yg==
 
-proxy-addr@~2.0.5:
+proxy-addr@~2.0.7:
   version "2.0.7"
   resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025"
   integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==
@@ -7199,30 +7196,18 @@ punycode@^2.1.0, punycode@^2.1.1:
   resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
   integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
 
-qs@6.7.0:
-  version "6.7.0"
-  resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
-  integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==
+qs@6.11.0, qs@^6.10.3, qs@^6.9.4:
+  version "6.11.0"
+  resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a"
+  integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==
+  dependencies:
+    side-channel "^1.0.4"
 
 qs@6.9.3:
   version "6.9.3"
   resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.3.tgz#bfadcd296c2d549f1dffa560619132c977f5008e"
   integrity sha512-EbZYNarm6138UKKq46tdx08Yo/q9ZhFoAXAI1meAFd2GtbRDhbZY2WQSICskT0c5q99aFzLG1D4nvTk9tqfXIw==
 
-qs@^6.10.3:
-  version "6.10.3"
-  resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.3.tgz#d6cde1b2ffca87b5aa57889816c5f81535e22e8e"
-  integrity sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==
-  dependencies:
-    side-channel "^1.0.4"
-
-qs@^6.9.4:
-  version "6.10.1"
-  resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.1.tgz#4931482fa8d647a5aab799c5271d2133b981fb6a"
-  integrity sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg==
-  dependencies:
-    side-channel "^1.0.4"
-
 query-string@^6.13.8:
   version "6.14.1"
   resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.14.1.tgz#7ac2dca46da7f309449ba0f86b1fd28255b0c86a"
@@ -7255,13 +7240,13 @@ range-parser@^1.2.1, range-parser@~1.2.1:
   resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
   integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
 
-raw-body@2.4.0:
-  version "2.4.0"
-  resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332"
-  integrity sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==
+raw-body@2.5.1:
+  version "2.5.1"
+  resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.1.tgz#fe1b1628b181b700215e5fd42389f98b71392857"
+  integrity sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==
   dependencies:
-    bytes "3.1.0"
-    http-errors "1.7.2"
+    bytes "3.1.2"
+    http-errors "2.0.0"
     iconv-lite "0.4.24"
     unpipe "1.0.0"
 
@@ -7803,7 +7788,7 @@ react-hot-loader@^3.1.3:
     redbox-react "^1.3.6"
     source-map "^0.6.1"
 
-react-is@^16.12.0, react-is@^16.13.1, react-is@^16.8.1:
+react-is@^16.12.0, react-is@^16.13.1:
   version "16.13.1"
   resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
   integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
@@ -7828,12 +7813,12 @@ react-moment@^0.9.7:
   resolved "https://registry.yarnpkg.com/react-moment/-/react-moment-0.9.7.tgz#ca570466595b1aa4f7619e62da18b3bb2de8b6f3"
   integrity sha512-ifzUrUGF6KRsUN2pRG5k56kO0mJBr8kRkWb0wNvtFIsBIxOuPxhUpL1YlXwpbQCbHq23hUu6A0VEk64HsFxk9g==
 
-react-paginate@^6.5.0:
-  version "6.5.0"
-  resolved "https://registry.yarnpkg.com/react-paginate/-/react-paginate-6.5.0.tgz#b9baf53627b115cfd688afa048776aa45bffda19"
-  integrity sha512-H7xSi9jyiJzgfaj+2nNhQcjZfwzJ/Mxb64V2RiyDctjZyCWojwsaGwMqhLBpQ58iAuMVtBMRQ7ECqMcUKG9QSQ==
+react-paginate@^8.1.4:
+  version "8.1.4"
+  resolved "https://registry.yarnpkg.com/react-paginate/-/react-paginate-8.1.4.tgz#f62bfef4f07bb420f3a994e0284d1065a865a6b9"
+  integrity sha512-c3rxjcTEqeDQa6LqXifxLeFguY2qy2CHGRphVjHLFFMGfIHyaJ+v3bOvIlLYEeohwQ1q+cQpknjsqBVrkc/SNA==
   dependencies:
-    prop-types "^15.6.1"
+    prop-types "^15"
 
 react-proxy@^3.0.0-alpha.0:
   version "3.0.0-alpha.1"
@@ -8252,7 +8237,7 @@ safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
   resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
   integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
 
-safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0:
+safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0:
   version "5.2.1"
   resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
   integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
@@ -8408,24 +8393,24 @@ semver@^7.3.7:
   dependencies:
     lru-cache "^6.0.0"
 
-send@0.17.1:
-  version "0.17.1"
-  resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8"
-  integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==
+send@0.18.0:
+  version "0.18.0"
+  resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be"
+  integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==
   dependencies:
     debug "2.6.9"
-    depd "~1.1.2"
-    destroy "~1.0.4"
+    depd "2.0.0"
+    destroy "1.2.0"
     encodeurl "~1.0.2"
     escape-html "~1.0.3"
     etag "~1.8.1"
     fresh "0.5.2"
-    http-errors "~1.7.2"
+    http-errors "2.0.0"
     mime "1.6.0"
-    ms "2.1.1"
-    on-finished "~2.3.0"
+    ms "2.1.3"
+    on-finished "2.4.1"
     range-parser "~1.2.1"
-    statuses "~1.5.0"
+    statuses "2.0.1"
 
 serialize-javascript@^5.0.1:
   version "5.0.1"
@@ -8454,15 +8439,15 @@ serve-index@^1.9.1:
     mime-types "~2.1.17"
     parseurl "~1.3.2"
 
-serve-static@1.14.1:
-  version "1.14.1"
-  resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9"
-  integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==
+serve-static@1.15.0:
+  version "1.15.0"
+  resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540"
+  integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==
   dependencies:
     encodeurl "~1.0.2"
     escape-html "~1.0.3"
     parseurl "~1.3.3"
-    send "0.17.1"
+    send "0.18.0"
 
 set-value@^2.0.0, set-value@^2.0.1:
   version "2.0.1"
@@ -8479,10 +8464,10 @@ setprototypeof@1.1.0:
   resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656"
   integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==
 
-setprototypeof@1.1.1:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683"
-  integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==
+setprototypeof@1.2.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424"
+  integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==
 
 shallow-clone@^3.0.0:
   version "3.0.1"
@@ -8785,7 +8770,12 @@ static-extend@^0.1.1:
     define-property "^0.2.5"
     object-copy "^0.1.0"
 
-"statuses@>= 1.4.0 < 2", "statuses@>= 1.5.0 < 2", statuses@~1.5.0:
+statuses@2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63"
+  integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==
+
+"statuses@>= 1.4.0 < 2":
   version "1.5.0"
   resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
   integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=
@@ -9161,10 +9151,10 @@ toggle-selection@^1.0.6:
   resolved "https://registry.yarnpkg.com/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32"
   integrity sha1-bkWxJj8gF/oKzH2J14sVuL932jI=
 
-toidentifier@1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
-  integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==
+toidentifier@1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35"
+  integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==
 
 tough-cookie@^4.0.0:
   version "4.0.0"
@@ -9254,12 +9244,12 @@ tslint-plugin-prettier@^2.0.1:
     lines-and-columns "^1.1.6"
     tslib "^1.7.1"
 
-tslint-react@^3.4.0:
-  version "3.6.0"
-  resolved "https://registry.yarnpkg.com/tslint-react/-/tslint-react-3.6.0.tgz#7f462c95c4a0afaae82507f06517ff02942196a1"
-  integrity sha512-AIv1QcsSnj7e9pFir6cJ6vIncTqxfqeFF3Lzh8SuuBljueYzEAtByuB6zMaD27BL0xhMEqsZ9s5eHuCONydjBw==
+tslint-react@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/tslint-react/-/tslint-react-5.0.0.tgz#d0ae644e8163bdd3e134012e9353094904e8dd44"
+  integrity sha512-/IbcSmoBPlFic8kQaRfQ4knTY4mivwo5LVzvozvX6Dyu2ynEnrh1dIcR2ujjyp/IodXqY/H5GbxFxSMo/Kf2Hg==
   dependencies:
-    tsutils "^2.13.1"
+    tsutils "^3.17.1"
 
 tslint@^6.1.3:
   version "6.1.3"
@@ -9280,13 +9270,20 @@ tslint@^6.1.3:
     tslib "^1.13.0"
     tsutils "^2.29.0"
 
-tsutils@^2.13.1, tsutils@^2.29.0:
+tsutils@^2.29.0:
   version "2.29.0"
   resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.29.0.tgz#32b488501467acbedd4b85498673a0812aca0b99"
   integrity sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==
   dependencies:
     tslib "^1.8.1"
 
+tsutils@^3.17.1:
+  version "3.21.0"
+  resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623"
+  integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==
+  dependencies:
+    tslib "^1.8.1"
+
 tsx@^3.4.0:
   version "3.4.0"
   resolved "https://registry.yarnpkg.com/tsx/-/tsx-3.4.0.tgz#20aed3831464c0183396d7c4f7c813a8c8429647"
@@ -9315,7 +9312,7 @@ type-fest@^0.21.3:
   resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37"
   integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==
 
-type-is@~1.6.17, type-is@~1.6.18:
+type-is@~1.6.18:
   version "1.6.18"
   resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"
   integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==
@@ -9402,7 +9399,7 @@ universalify@^0.1.2:
 unpipe@1.0.0, unpipe@~1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
-  integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=
+  integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==
 
 unset-value@^1.0.0:
   version "1.0.0"
@@ -9467,7 +9464,7 @@ utila@~0.4:
 utils-merge@1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
-  integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=
+  integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==
 
 uuid@^3.3.2:
   version "3.3.3"
@@ -9501,7 +9498,7 @@ value-equal@^1.0.1:
 vary@~1.1.2:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
-  integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=
+  integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==
 
 w3c-hr-time@^1.0.2:
   version "1.0.2"
diff --git a/util/helm/cmd.go b/util/helm/cmd.go
index 1f6c68efc..db98c0fce 100644
--- a/util/helm/cmd.go
+++ b/util/helm/cmd.go
@@ -89,26 +89,6 @@ func (c *Cmd) RegistryLogin(repo string, creds Creds) (string, error) {
 		args = append(args, "--password", creds.Password)
 	}
 
-	if creds.CAPath != "" {
-		args = append(args, "--ca-file", creds.CAPath)
-	}
-	if len(creds.CertData) > 0 {
-		filePath, closer, err := writeToTmp(creds.CertData)
-		if err != nil {
-			return "", err
-		}
-		defer argoio.Close(closer)
-		args = append(args, "--cert-file", filePath)
-	}
-	if len(creds.KeyData) > 0 {
-		filePath, closer, err := writeToTmp(creds.KeyData)
-		if err != nil {
-			return "", err
-		}
-		defer argoio.Close(closer)
-		args = append(args, "--key-file", filePath)
-	}
-
 	if creds.InsecureSkipVerify {
 		args = append(args, "--insecure")
 	}
@@ -119,26 +99,6 @@ func (c *Cmd) RegistryLogout(repo string, creds Creds) (string, error) {
 	args := []string{"registry", "logout"}
 	args = append(args, repo)
 
-	if creds.CAPath != "" {
-		args = append(args, "--ca-file", creds.CAPath)
-	}
-	if len(creds.CertData) > 0 {
-		filePath, closer, err := writeToTmp(creds.CertData)
-		if err != nil {
-			return "", err
-		}
-		defer argoio.Close(closer)
-		args = append(args, "--cert-file", filePath)
-	}
-	if len(creds.KeyData) > 0 {
-		filePath, closer, err := writeToTmp(creds.KeyData)
-		if err != nil {
-			return "", err
-		}
-		defer argoio.Close(closer)
-		args = append(args, "--key-file", filePath)
-	}
-
 	return c.run(args...)
 }
 
diff --git a/util/settings/settings.go b/util/settings/settings.go
index 80a1c6da4..b239d1f2c 100644
--- a/util/settings/settings.go
+++ b/util/settings/settings.go
@@ -110,6 +110,9 @@ type ArgoCDSettings struct {
 	OIDCTLSInsecureSkipVerify bool `json:"oidcTLSInsecureSkipVerify"`
 	// AppsInAnyNamespaceEnabled indicates whether applications are allowed to be created in any namespace
 	AppsInAnyNamespaceEnabled bool `json:"appsInAnyNamespaceEnabled"`
+	// ExtensionConfig configurations related to ArgoCD proxy extensions. The value
+	// is a yaml string defined in extension.ExtensionConfigs struct.
+	ExtensionConfig string `json:"extensionConfig,omitempty"`
 }
 
 type GoogleAnalytics struct {
@@ -318,6 +321,20 @@ type RepositoryCredentials struct {
 	Type string `json:"type,omitempty"`
 }
 
+// DeepLink structure
+type DeepLink struct {
+	// URL that the deep link will redirect to
+	URL string `json:"url"`
+	// Title that will be displayed in the UI corresponding to that link
+	Title string `json:"title"`
+	// Description (optional) a description for what the deep link is about
+	Description *string `json:"description,omitempty"`
+	// IconClass (optional) a font-awesome icon class to be used when displaying the links in dropdown menus.
+	IconClass *string `json:"icon.class,omitempty"`
+	// Condition (optional) a conditional statement depending on which the deep link shall be rendered
+	Condition *string `json:"if,omitempty"`
+}
+
 const (
 	// settingServerSignatureKey designates the key for a server secret key inside a Kubernetes secret.
 	settingServerSignatureKey = "server.secretkey"
@@ -423,6 +440,13 @@ const (
 	execShellsKey = "exec.shells"
 	// oidcTLSInsecureSkipVerifyKey is the key to configure whether TLS cert verification is skipped for OIDC connections
 	oidcTLSInsecureSkipVerifyKey = "oidc.tls.insecure.skip.verify"
+	// ApplicationDeepLinks is the application deep link key
+	ApplicationDeepLinks = "application.links"
+	// ProjectDeepLinks is the project deep link key
+	ProjectDeepLinks = "project.links"
+	// ResourceDeepLinks is the resource deep link key
+	ResourceDeepLinks = "resource.links"
+	extensionConfig   = "extension.config"
 )
 
 var (
@@ -701,6 +725,21 @@ func (mgr *SettingsManager) GetConfigManagementPlugins() ([]v1alpha1.ConfigManag
 	return plugins, nil
 }
 
+func (mgr *SettingsManager) GetDeepLinks(deeplinkType string) ([]DeepLink, error) {
+	argoCDCM, err := mgr.getConfigMap()
+	if err != nil {
+		return nil, err
+	}
+	deepLinks := make([]DeepLink, 0)
+	if value, ok := argoCDCM.Data[deeplinkType]; ok {
+		err := yaml.Unmarshal([]byte(value), &deepLinks)
+		if err != nil {
+			return nil, err
+		}
+	}
+	return deepLinks, nil
+}
+
 func (mgr *SettingsManager) GetEnabledSourceTypes() (map[string]bool, error) {
 	argoCDCM, err := mgr.getConfigMap()
 	if err != nil {
@@ -1294,6 +1333,7 @@ func updateSettingsFromConfigMap(settings *ArgoCDSettings, argoCDCM *apiv1.Confi
 	}
 	settings.TrackingMethod = argoCDCM.Data[settingsResourceTrackingMethodKey]
 	settings.OIDCTLSInsecureSkipVerify = argoCDCM.Data[oidcTLSInsecureSkipVerifyKey] == "true"
+	settings.ExtensionConfig = argoCDCM.Data[extensionConfig]
 }
 
 // validateExternalURL ensures the external URL that is set on the configmap is valid


Test patch:
diff --git a/pkg/apiclient/application/forwarder_overwrite_test.go b/pkg/apiclient/application/forwarder_overwrite_test.go
new file mode 100644
index 000000000..370a84173
--- /dev/null
+++ b/pkg/apiclient/application/forwarder_overwrite_test.go
@@ -0,0 +1,53 @@
+package application
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/require"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+
+	"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
+	"github.com/argoproj/argo-cd/v2/test"
+)
+
+func TestProcessApplicationListField_SyncOperation(t *testing.T) {
+	list := v1alpha1.ApplicationList{
+		Items: []v1alpha1.Application{{Operation: &v1alpha1.Operation{Sync: &v1alpha1.SyncOperation{
+			Revision: "abc",
+		}}}},
+	}
+
+	res, err := processApplicationListField(&list, map[string]interface{}{"items.operation.sync": true}, false)
+	require.NoError(t, err)
+	resMap, ok := res.(map[string]interface{})
+	require.True(t, ok)
+
+	items, ok := resMap["items"].([]map[string]interface{})
+	require.True(t, ok)
+	item := test.ToMap(items[0])
+
+	val, ok, err := unstructured.NestedString(item, "operation", "sync", "revision")
+	require.NoError(t, err)
+	require.True(t, ok)
+
+	require.Equal(t, "abc", val)
+}
+
+func TestProcessApplicationListField_SyncOperationMissing(t *testing.T) {
+	list := v1alpha1.ApplicationList{
+		Items: []v1alpha1.Application{{Operation: nil}},
+	}
+
+	res, err := processApplicationListField(&list, map[string]interface{}{"items.operation.sync": true}, false)
+	require.NoError(t, err)
+	resMap, ok := res.(map[string]interface{})
+	require.True(t, ok)
+
+	items, ok := resMap["items"].([]map[string]interface{})
+	require.True(t, ok)
+	item := test.ToMap(items[0])
+
+	_, ok, err = unstructured.NestedString(item, "operation")
+	require.NoError(t, err)
+	require.False(t, ok)
+}
diff --git a/pkg/apis/application/v1alpha1/types_test.go b/pkg/apis/application/v1alpha1/types_test.go
index ca7703687..e69127f0d 100644
--- a/pkg/apis/application/v1alpha1/types_test.go
+++ b/pkg/apis/application/v1alpha1/types_test.go
@@ -64,6 +64,47 @@ func TestAppProject_IsSourcePermitted(t *testing.T) {
 	}
 }
 
+func TestAppProject_IsNegatedSourcePermitted(t *testing.T) {
+	testData := []struct {
+		projSources []string
+		appSource   string
+		isPermitted bool
+	}{{
+		projSources: []string{"!https://github.com/argoproj/test.git"}, appSource: "https://github.com/argoproj/test.git", isPermitted: false,
+	}, {
+		projSources: []string{"!ssh://git@GITHUB.com:argoproj/test"}, appSource: "ssh://git@github.com:argoproj/test", isPermitted: false,
+	}, {
+		projSources: []string{"!https://github.com/argoproj/*"}, appSource: "https://github.com/argoproj/argoproj.git", isPermitted: false,
+	}, {
+		projSources: []string{"https://github.com/test1/test.git", "!https://github.com/test2/test.git"}, appSource: "https://github.com/test2/test.git", isPermitted: false,
+	}, {
+		projSources: []string{"!https://github.com/argoproj/foo*"}, appSource: "https://github.com/argoproj/foo1", isPermitted: false,
+	}, {
+		projSources: []string{"!https://gitlab.com/group/*/*"}, appSource: "https://gitlab.com/group/repo/owner", isPermitted: false,
+	}, {
+		projSources: []string{"!https://gitlab.com/group/*/*/*"}, appSource: "https://gitlab.com/group/sub-group/repo/owner", isPermitted: false,
+	}, {
+		projSources: []string{"!https://gitlab.com/group/**"}, appSource: "https://gitlab.com/group/sub-group/repo/owner", isPermitted: false,
+	}, {
+		projSources: []string{"*"}, appSource: "https://github.com/argoproj/test.git", isPermitted: true,
+	}, {
+		projSources: []string{"https://github.com/argoproj/test1.git", "*"}, appSource: "https://github.com/argoproj/test2.git", isPermitted: true,
+	}, {
+		projSources: []string{"!https://github.com/argoproj/*.git", "*"}, appSource: "https://github.com/argoproj1/test2.git", isPermitted: true,
+	}}
+
+	for _, data := range testData {
+		proj := AppProject{
+			Spec: AppProjectSpec{
+				SourceRepos: data.projSources,
+			},
+		}
+		assert.Equal(t, proj.IsSourcePermitted(ApplicationSource{
+			RepoURL: data.appSource,
+		}), data.isPermitted)
+	}
+}
+
 func TestAppProject_IsDestinationPermitted(t *testing.T) {
 	testData := []struct {
 		projDest    []ApplicationDestination
@@ -509,6 +550,29 @@ func newTestProject() *AppProject {
 	return &p
 }
 
+// TestAppProject_ValidateSources tests for an invalid source
+func TestAppProject_ValidateSources(t *testing.T) {
+	p := newTestProject()
+	err := p.ValidateProject()
+	assert.NoError(t, err)
+	badSources := []string{
+		"!*",
+	}
+	for _, badName := range badSources {
+		p.Spec.SourceRepos = []string{badName}
+		err = p.ValidateProject()
+		assert.Error(t, err)
+	}
+
+	duplicateSources := []string{
+		"foo",
+		"foo",
+	}
+	p.Spec.SourceRepos = duplicateSources
+	err = p.ValidateProject()
+	assert.Error(t, err)
+}
+
 // TestAppProject_ValidateDestinations tests for an invalid destination
 func TestAppProject_ValidateDestinations(t *testing.T) {
 	p := newTestProject()
@@ -3164,7 +3228,7 @@ func Test_RBACName(t *testing.T) {
 func TestApplicationSourcePluginParameters_Environ_string(t *testing.T) {
 	params := ApplicationSourcePluginParameters{
 		{
-			Name: "version",
+			Name:    "version",
 			String_: pointer.String("1.2.3"),
 		},
 	}
@@ -3180,7 +3244,7 @@ func TestApplicationSourcePluginParameters_Environ_string(t *testing.T) {
 func TestApplicationSourcePluginParameters_Environ_array(t *testing.T) {
 	params := ApplicationSourcePluginParameters{
 		{
-			Name: "dependencies",
+			Name:  "dependencies",
 			Array: []string{"redis", "minio"},
 		},
 	}
@@ -3200,7 +3264,7 @@ func TestApplicationSourcePluginParameters_Environ_map(t *testing.T) {
 			Name: "helm-parameters",
 			Map: map[string]string{
 				"image.repo": "quay.io/argoproj/argo-cd",
-				"image.tag": "v2.4.0",
+				"image.tag":  "v2.4.0",
 			},
 		},
 	}
@@ -3219,12 +3283,12 @@ func TestApplicationSourcePluginParameters_Environ_all(t *testing.T) {
 	// Name collisions can happen for the convenience env vars. When in doubt, CMP authors should use the JSON env var.
 	params := ApplicationSourcePluginParameters{
 		{
-			Name: "some-name",
+			Name:    "some-name",
 			String_: pointer.String("1.2.3"),
-			Array: []string{"redis", "minio"},
+			Array:   []string{"redis", "minio"},
 			Map: map[string]string{
 				"image.repo": "quay.io/argoproj/argo-cd",
-				"image.tag": "v2.4.0",
+				"image.tag":  "v2.4.0",
 			},
 		},
 	}
@@ -3240,4 +3304,3 @@ func TestApplicationSourcePluginParameters_Environ_all(t *testing.T) {
 	require.NoError(t, err)
 	assert.Contains(t, environ, fmt.Sprintf("ARGOCD_APP_PARAMETERS=%s", paramsJson))
 }
-
diff --git a/reposerver/repository/repository_test.go b/reposerver/repository/repository_test.go
index d8cbab3cd..ef1d4a2ed 100644
--- a/reposerver/repository/repository_test.go
+++ b/reposerver/repository/repository_test.go
@@ -47,14 +47,14 @@ gpg:                using RSA key 4AEE18F83AFDEB23
 gpg: Good signature from "GitHub (web-flow commit signing) <noreply@github.com>" [ultimate]
 `
 
-type clientFunc func(*gitmocks.Client)
+type clientFunc func(*gitmocks.Client, *helmmocks.Client)
 
 func newServiceWithMocks(root string, signed bool) (*Service, *gitmocks.Client) {
 	root, err := filepath.Abs(root)
 	if err != nil {
 		panic(err)
 	}
-	return newServiceWithOpt(func(gitClient *gitmocks.Client) {
+	return newServiceWithOpt(func(gitClient *gitmocks.Client, helmClient *helmmocks.Client) {
 		gitClient.On("Init").Return(nil)
 		gitClient.On("Fetch", mock.Anything).Return(nil)
 		gitClient.On("Checkout", mock.Anything, mock.Anything).Return(nil)
@@ -66,31 +66,32 @@ func newServiceWithMocks(root string, signed bool) (*Service, *gitmocks.Client)
 		} else {
 			gitClient.On("VerifyCommitSignature", mock.Anything).Return("", nil)
 		}
+
+		chart := "my-chart"
+		oobChart := "out-of-bounds-chart"
+		version := "1.1.0"
+		helmClient.On("GetIndex", true).Return(&helm.Index{Entries: map[string]helm.Entries{
+			chart:    {{Version: "1.0.0"}, {Version: version}},
+			oobChart: {{Version: "1.0.0"}, {Version: version}},
+		}}, nil)
+		helmClient.On("ExtractChart", chart, version).Return("./testdata/my-chart", io.NopCloser, nil)
+		helmClient.On("ExtractChart", oobChart, version).Return("./testdata2/out-of-bounds-chart", io.NopCloser, nil)
+		helmClient.On("CleanChartCache", chart, version).Return(nil)
+		helmClient.On("CleanChartCache", oobChart, version).Return(nil)
+		helmClient.On("DependencyBuild").Return(nil)
 	}, root)
 }
 
 func newServiceWithOpt(cf clientFunc, root string) (*Service, *gitmocks.Client) {
 	helmClient := &helmmocks.Client{}
 	gitClient := &gitmocks.Client{}
-	cf(gitClient)
+	cf(gitClient, helmClient)
 	service := NewService(metrics.NewMetricsServer(), cache.NewCache(
 		cacheutil.NewCache(cacheutil.NewInMemoryCache(1*time.Minute)),
 		1*time.Minute,
 		1*time.Minute,
 	), RepoServerInitConstants{ParallelismLimit: 1}, argo.NewResourceTracking(), &git.NoopCredsStore{}, root)
 
-	chart := "my-chart"
-	oobChart := "out-of-bounds-chart"
-	version := "1.1.0"
-	helmClient.On("GetIndex", true).Return(&helm.Index{Entries: map[string]helm.Entries{
-		chart:    {{Version: "1.0.0"}, {Version: version}},
-		oobChart: {{Version: "1.0.0"}, {Version: version}},
-	}}, nil)
-	helmClient.On("ExtractChart", chart, version).Return("./testdata/my-chart", io.NopCloser, nil)
-	helmClient.On("ExtractChart", oobChart, version).Return("./testdata2/out-of-bounds-chart", io.NopCloser, nil)
-	helmClient.On("CleanChartCache", chart, version).Return(nil)
-	helmClient.On("CleanChartCache", oobChart, version).Return(nil)
-
 	service.newGitClient = func(rawRepoURL string, root string, creds git.Creds, insecure bool, enableLfs bool, prosy string, opts ...git.ClientOpts) (client git.Client, e error) {
 		return gitClient, nil
 	}
@@ -121,7 +122,7 @@ func newServiceWithCommitSHA(root, revision string) *Service {
 		revisionErr = errors.New("not a commit SHA")
 	}
 
-	service, gitClient := newServiceWithOpt(func(gitClient *gitmocks.Client) {
+	service, gitClient := newServiceWithOpt(func(gitClient *gitmocks.Client, helmClient *helmmocks.Client) {
 		gitClient.On("Init").Return(nil)
 		gitClient.On("Fetch", mock.Anything).Return(nil)
 		gitClient.On("Checkout", mock.Anything, mock.Anything).Return(nil)
@@ -1190,6 +1191,7 @@ func TestListApps(t *testing.T) {
 		"kustomization_yml":                 "Kustomize",
 		"my-chart":                          "Helm",
 		"my-chart-2":                        "Helm",
+		"oci-dependencies":                  "Helm",
 		"out-of-bounds-values-file-link":    "Helm",
 		"values-files":                      "Helm",
 	}
@@ -2519,3 +2521,18 @@ func Test_populateHelmAppDetails_values_symlinks(t *testing.T) {
 		assert.Empty(t, res.Helm.Parameters)
 	})
 }
+
+func TestOCIDependencies(t *testing.T) {
+	src := argoappv1.ApplicationSource{Path: "."}
+	q := apiclient.ManifestRequest{Repo: &argoappv1.Repository{}, ApplicationSource: &src, HelmRepoCreds: []*argoappv1.RepoCreds{
+		{URL: "example.com", Username: "test", Password: "test", EnableOCI: true},
+	}}
+
+	err := populateRequestRepos("./testdata/oci-dependencies", &q)
+	assert.Nil(t, err)
+
+	assert.Equal(t, len(q.Repos), 1)
+	assert.Equal(t, q.Repos[0].Username, "test")
+	assert.Equal(t, q.Repos[0].EnableOCI, true)
+	assert.Equal(t, q.Repos[0].Repo, "example.com")
+}
diff --git a/reposerver/repository/testdata/oci-dependencies/Chart.yaml b/reposerver/repository/testdata/oci-dependencies/Chart.yaml
new file mode 100644
index 000000000..3b39781ed
--- /dev/null
+++ b/reposerver/repository/testdata/oci-dependencies/Chart.yaml
@@ -0,0 +1,6 @@
+name: my-chart
+version: 1.1.0
+dependencies:
+- name: my-dependency
+  repository: oci://example.com
+  version: '*'
\ No newline at end of file
diff --git a/server/deeplinks/deeplinks_test.go b/server/deeplinks/deeplinks_test.go
new file mode 100644
index 000000000..9fc703a7e
--- /dev/null
+++ b/server/deeplinks/deeplinks_test.go
@@ -0,0 +1,101 @@
+package deeplinks
+
+import (
+	"github.com/argoproj/argo-cd/v2/pkg/apiclient/application"
+	"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
+	"github.com/argoproj/argo-cd/v2/util/settings"
+	"github.com/argoproj/gitops-engine/pkg/utils/kube"
+	"github.com/stretchr/testify/assert"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/utils/pointer"
+	"reflect"
+	"strings"
+	"testing"
+)
+
+type deepLinkTC struct {
+	obj         *unstructured.Unstructured
+	inputLinks  []settings.DeepLink
+	outputLinks []*application.LinkInfo
+	error       []string
+}
+
+func TestDeepLinks(t *testing.T) {
+	appObj, err := kube.ToUnstructured(&v1alpha1.Application{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "test",
+			Namespace: "test",
+		},
+		Spec: v1alpha1.ApplicationSpec{
+			Destination: v1alpha1.ApplicationDestination{
+				Server:    "test.com",
+				Namespace: "testns",
+			},
+		},
+	})
+	assert.NoError(t, err)
+	testTable := []deepLinkTC{
+		{
+			obj: appObj,
+			inputLinks: []settings.DeepLink{{
+				Title: "link",
+				URL:   "http://example.com/{{ .metadata.name }}&{{ .spec.destination.namespace }}",
+			}},
+			outputLinks: []*application.LinkInfo{{
+				Title: pointer.String("link"),
+				Url:   pointer.String("http://example.com/test&testns"),
+			}},
+			error: []string{},
+		},
+		{
+			obj: appObj,
+			inputLinks: []settings.DeepLink{
+				{
+					Title:     "link",
+					URL:       "http://example.com/{{ .metadata.name }}&{{ .spec.destination.namespace }}",
+					Condition: pointer.String(`metadata.name matches "test"`),
+				},
+				{
+					Title:     "link1",
+					URL:       "http://example.com/{{ .metadata.name }}&{{ .spec.destination.namespace }}",
+					Condition: pointer.String(`metadata.name matches "test1"`),
+				},
+				{
+					Title:     "link2",
+					URL:       "http://example.com/{{ .metadata.name }}&{{ .spec.destination.namespace }}",
+					Condition: pointer.String(`metadata.test matches "test"`),
+				}},
+			outputLinks: []*application.LinkInfo{{
+				Title: pointer.String("link"),
+				Url:   pointer.String("http://example.com/test&testns"),
+			}},
+			error: []string{"failed to evaluate link condition 'metadata.test matches \"test\"' with resource test, error=interface conversion: interface {} is nil, not string (1:15)\n | metadata.test matches \"test\"\n | ..............^"},
+		},
+		{
+			obj: appObj,
+			inputLinks: []settings.DeepLink{
+				{
+					Title:     "link",
+					URL:       "http://example.com/{{ .metadata.name }}&{{ .spec.destination.namespace }}",
+					Condition: pointer.String(`metadata.name matches "test"`),
+				},
+				{
+					Title:     "link1",
+					URL:       "http://example.com/{{ .metadata.name }}&{{ .spec.destination.namespace }}",
+					Condition: pointer.String(`1 + 1`),
+				}},
+			outputLinks: []*application.LinkInfo{{
+				Title: pointer.String("link"),
+				Url:   pointer.String("http://example.com/test&testns"),
+			}},
+			error: []string{"link condition '1 + 1' evaluated to non-boolean value for resource test"},
+		},
+	}
+
+	for _, tc := range testTable {
+		output, err := EvaluateDeepLinksResponse(*tc.obj, tc.inputLinks)
+		assert.Equal(t, tc.error, err, strings.Join(err, ","))
+		assert.Equal(t, reflect.DeepEqual(output.Items, tc.outputLinks), true)
+	}
+}
diff --git a/server/extension/extension_test.go b/server/extension/extension_test.go
new file mode 100644
index 000000000..cc15369f5
--- /dev/null
+++ b/server/extension/extension_test.go
@@ -0,0 +1,341 @@
+package extension_test
+
+import (
+	"context"
+	"fmt"
+	"io/ioutil"
+	"net/http"
+	"net/http/httptest"
+	"strings"
+	"testing"
+
+	v1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
+	"github.com/argoproj/argo-cd/v2/server/extension"
+	"github.com/argoproj/argo-cd/v2/server/extension/mocks"
+	"github.com/argoproj/argo-cd/v2/util/settings"
+	"github.com/gorilla/mux"
+	"github.com/sirupsen/logrus/hooks/test"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/mock"
+	"github.com/stretchr/testify/require"
+)
+
+func TestRegisterHandlers(t *testing.T) {
+	type fixture struct {
+		settingsGetterMock *mocks.SettingsGetter
+		manager            *extension.Manager
+	}
+
+	setup := func() *fixture {
+		settMock := &mocks.SettingsGetter{}
+
+		logger, _ := test.NewNullLogger()
+		logEntry := logger.WithContext(context.Background())
+		m := extension.NewManager(settMock, nil, logEntry)
+
+		return &fixture{
+			settingsGetterMock: settMock,
+			manager:            m,
+		}
+	}
+	t.Run("will register handlers successfully", func(t *testing.T) {
+		// given
+		f := setup()
+		router := mux.NewRouter()
+		settings := &settings.ArgoCDSettings{
+			ExtensionConfig: getExtensionConfigString(),
+		}
+		f.settingsGetterMock.On("Get", mock.Anything).Return(settings, nil)
+		expectedRegexRoutes := []string{
+			"^/extensions/",
+			"^/extensions/external-backend/",
+			"^/extensions/some-backend/",
+			"^/extensions/$"}
+
+		// when
+		err := f.manager.RegisterHandlers(router)
+
+		// then
+		require.NoError(t, err)
+		walkFn := func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error {
+			pathRegex, err := route.GetPathRegexp()
+			require.NoError(t, err)
+			assert.Contains(t, expectedRegexRoutes, pathRegex)
+			return nil
+		}
+		err = router.Walk(walkFn)
+		assert.NoError(t, err)
+	})
+	t.Run("will return error if extension config is invalid", func(t *testing.T) {
+		// given
+		type testCase struct {
+			name       string
+			configYaml string
+		}
+		cases := []testCase{
+			{
+				name:       "no config",
+				configYaml: "",
+			},
+			{
+				name:       "no name",
+				configYaml: getExtensionConfigNoName(),
+			},
+			{
+				name:       "no URL",
+				configYaml: getExtensionConfigNoURL(),
+			},
+			{
+				name:       "invalid name",
+				configYaml: getExtensionConfigInvalidName(),
+			},
+		}
+
+		// when
+		for _, tc := range cases {
+			tc := tc
+			t.Run(tc.name, func(t *testing.T) {
+				// given
+				f := setup()
+				router := mux.NewRouter()
+				settings := &settings.ArgoCDSettings{
+					ExtensionConfig: tc.configYaml,
+				}
+				f.settingsGetterMock.On("Get", mock.Anything).Return(settings, nil)
+
+				// when
+				err := f.manager.RegisterHandlers(router)
+
+				// then
+				assert.Error(t, err)
+			})
+		}
+	})
+}
+
+func TestExtensionsHandlers(t *testing.T) {
+	type fixture struct {
+		router             *mux.Router
+		appGetterMock      *mocks.ApplicationGetter
+		settingsGetterMock *mocks.SettingsGetter
+		manager            *extension.Manager
+	}
+
+	setup := func() *fixture {
+		appMock := &mocks.ApplicationGetter{}
+		settMock := &mocks.SettingsGetter{}
+
+		logger, _ := test.NewNullLogger()
+		logEntry := logger.WithContext(context.Background())
+		m := extension.NewManager(settMock, appMock, logEntry)
+
+		router := mux.NewRouter()
+
+		return &fixture{
+			router:             router,
+			appGetterMock:      appMock,
+			settingsGetterMock: settMock,
+			manager:            m,
+		}
+	}
+
+	withExtensionConfig := func(configYaml string, f *fixture) {
+		settings := &settings.ArgoCDSettings{
+			ExtensionConfig: configYaml,
+		}
+		f.settingsGetterMock.On("Get", mock.Anything).Return(settings, nil)
+	}
+
+	startTestServer := func(t *testing.T, f *fixture) *httptest.Server {
+		err := f.manager.RegisterHandlers(f.router)
+		if err != nil {
+			t.Fatalf("error starting test server: %s", err)
+		}
+		return httptest.NewServer(f.router)
+	}
+
+	startBackendTestSrv := func(response string) *httptest.Server {
+		return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+			fmt.Fprintln(w, response)
+		}))
+
+	}
+	t.Run("proxy will return 404 if no extension endpoint is registered", func(t *testing.T) {
+		// given
+		f := setup()
+		withExtensionConfig(getExtensionConfigString(), f)
+		ts := startTestServer(t, f)
+		defer ts.Close()
+		nonRegisteredEndpoint := "non-registered"
+
+		// when
+		resp, err := http.Get(fmt.Sprintf("%s/extensions/%s/", ts.URL, nonRegisteredEndpoint))
+
+		// then
+		require.NoError(t, err)
+		require.NotNil(t, resp)
+		assert.Equal(t, http.StatusNotFound, resp.StatusCode)
+	})
+	t.Run("will call extension backend successfully", func(t *testing.T) {
+		// given
+		f := setup()
+		backendResponse := "some data"
+		backendEndpoint := "some-backend"
+		backendSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+			fmt.Fprintln(w, backendResponse)
+		}))
+		defer backendSrv.Close()
+		withExtensionConfig(getExtensionConfig(backendEndpoint, backendSrv.URL), f)
+		ts := startTestServer(t, f)
+		defer ts.Close()
+
+		// when
+		resp, err := http.Get(fmt.Sprintf("%s/extensions/%s/", ts.URL, backendEndpoint))
+
+		// then
+		require.NoError(t, err)
+		require.NotNil(t, resp)
+		require.Equal(t, http.StatusOK, resp.StatusCode)
+		body, err := ioutil.ReadAll(resp.Body)
+		require.NoError(t, err)
+		actual := strings.TrimSuffix(string(body), "\n")
+		assert.Equal(t, backendResponse, actual)
+	})
+	t.Run("will route requests with 2 backends for the same extension successfully", func(t *testing.T) {
+		// given
+		f := setup()
+		extName := "some-extension"
+
+		response1 := "response backend 1"
+		cluster1 := "cluster1"
+		beSrv1 := startBackendTestSrv(response1)
+		defer beSrv1.Close()
+
+		response2 := "response backend 2"
+		cluster2 := "cluster2"
+		beSrv2 := startBackendTestSrv(response2)
+		defer beSrv2.Close()
+
+		withExtensionConfig(getExtensionConfigWith2Backends(extName, beSrv1.URL, cluster1, beSrv2.URL, cluster2), f)
+		ts := startTestServer(t, f)
+		defer ts.Close()
+
+		app1 := &v1alpha1.Application{
+			Spec: v1alpha1.ApplicationSpec{
+				Destination: v1alpha1.ApplicationDestination{
+					Server: beSrv1.URL,
+					Name:   cluster1,
+				},
+			},
+		}
+		f.appGetterMock.On("Get", "ns1", "app1").Return(app1, nil)
+
+		app2 := &v1alpha1.Application{
+			Spec: v1alpha1.ApplicationSpec{
+				Destination: v1alpha1.ApplicationDestination{
+					Server: beSrv2.URL,
+					Name:   cluster2,
+				},
+			},
+		}
+		f.appGetterMock.On("Get", "ns2", "app2").Return(app2, nil)
+
+		url := fmt.Sprintf("%s/extensions/%s/", ts.URL, extName)
+		req, err := http.NewRequest(http.MethodGet, url, nil)
+		if err != nil {
+			t.Fatalf("error creating request: %s", err)
+		}
+		req1 := req.Clone(context.Background())
+		req1.Header.Add(extension.HeaderArgoCDApplicationName, "ns1/app1")
+		req2 := req.Clone(context.Background())
+		req2.Header.Add(extension.HeaderArgoCDApplicationName, "ns2/app2")
+
+		// when
+		resp1, err := http.DefaultClient.Do(req1)
+		require.NoError(t, err)
+		resp2, err := http.DefaultClient.Do(req2)
+		require.NoError(t, err)
+
+		// then
+		require.NotNil(t, resp1)
+		require.Equal(t, http.StatusOK, resp1.StatusCode)
+		body, err := ioutil.ReadAll(resp1.Body)
+		require.NoError(t, err)
+		actual := strings.TrimSuffix(string(body), "\n")
+		assert.Equal(t, response1, actual)
+
+		require.NotNil(t, resp2)
+		require.Equal(t, http.StatusOK, resp2.StatusCode)
+		body, err = ioutil.ReadAll(resp2.Body)
+		require.NoError(t, err)
+		actual = strings.TrimSuffix(string(body), "\n")
+		assert.Equal(t, response2, actual)
+	})
+}
+
+func getExtensionConfig(name, url string) string {
+	cfg := `
+extensions:
+- name: %s
+  backend:
+    services:
+    - url: %s
+`
+	return fmt.Sprintf(cfg, name, url)
+}
+
+func getExtensionConfigWith2Backends(name, url1, clus1, url2, clus2 string) string {
+	cfg := `
+extensions:
+- name: %s
+  backend:
+    services:
+    - url: %s
+      cluster: %s
+    - url: %s
+      cluster: %s
+`
+	return fmt.Sprintf(cfg, name, url1, clus1, url2, clus2)
+}
+
+func getExtensionConfigString() string {
+	return `
+extensions:
+- name: external-backend
+  backend:
+    services:
+    - url: https://httpbin.org
+- name: some-backend
+  backend:
+    services:
+    - url: http://localhost:7777
+`
+}
+
+func getExtensionConfigNoName() string {
+	return `
+extensions:
+- backend:
+    services:
+    - url: https://httpbin.org
+`
+}
+func getExtensionConfigInvalidName() string {
+	return `
+extensions:
+- name: invalid/name
+  backend:
+    services:
+    - url: https://httpbin.org
+`
+}
+
+func getExtensionConfigNoURL() string {
+	return `
+extensions:
+- name: some-backend
+  backend:
+    services:
+    - cluster: some-cluster
+`
+}
diff --git a/test/testutil.go b/test/testutil.go
index 6f8a74f8c..bccf0cef2 100644
--- a/test/testutil.go
+++ b/test/testutil.go
@@ -2,6 +2,7 @@ package test
 
 import (
 	"context"
+	"encoding/json"
 	"fmt"
 	"log"
 	"net"
@@ -82,6 +83,20 @@ func YamlToUnstructured(yamlStr string) *unstructured.Unstructured {
 	return &unstructured.Unstructured{Object: obj}
 }
 
+// ToMap converts any object to a map[string]interface{}
+func ToMap(obj interface{}) map[string]interface{} {
+	data, err := json.Marshal(obj)
+	if err != nil {
+		panic(err)
+	}
+	var res map[string]interface{}
+	err = json.Unmarshal(data, &res)
+	if err != nil {
+		panic(err)
+	}
+	return res
+}
+
 // GetTestDir will return the full directory path of the
 // calling test file.
 func GetTestDir(t *testing.T) string {