Repository: argoproj/argo-cd
Pull request #11566 (2022-12-05 21:20:22)

docs: add warning for user when using replace sync option
Signed-off-by: ashutosh16 <11219262+ashutosh16@users.noreply.github.com>

Note on DCO:

If the DCO action in the integration test fails, one or more of your commits are not signed off. Please click on the *Details* link next to the DCO action for instructions on how to resolve this.

Checklist:

* [ ] Either (a) I've created an [enhancement proposal](https://github.com/argoproj/argo-cd/issues/new/choose) and discussed it with the community, (b) this is a bug fix, or (c) this does not need to be in the release notes.
* [ ] The title of the PR states what changed and the related issues number (used for the release note).
* [ ] I've included "Closes [ISSUE #]" or "Fixes [ISSUE #]" in the description to automatically close the associated issue.
* [ ] I've updated both the CLI and UI to expose my feature, or I plan to submit a second PR with them.
* [ ] Does this PR require documentation updates?
* [ ] I've updated documentation as required by this PR.
* [ ] Optional. My organization is added to USERS.md.
* [ ] I have signed off all my commits as required by [DCO](https://github.com/argoproj/argoproj/tree/master/community#contributing-to-argo)
* [ ] I have written unit and/or e2e tests for my change. PRs without these are unlikely to be merged.
* [ ] My build is green ([troubleshooting builds](https://argo-cd.readthedocs.io/en/latest/developer-guide/ci/)). 



codecov[bot]: # [Codecov](https://codecov.io/gh/argoproj/argo-cd/pull/11566?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=argoproj) Report
Base: **46.85**% // Head: **46.85**% // No change to project coverage :thumbsup:
> Coverage data is based on head [(`6888c8b`)](https://codecov.io/gh/argoproj/argo-cd/pull/11566?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=argoproj) compared to base [(`f25f0b2`)](https://codecov.io/gh/argoproj/argo-cd/commit/f25f0b2cee2c9414ad7dd45f23c77f4b48daa878?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=argoproj).
> Patch coverage: 0.00% of modified lines in pull request are covered.

<details><summary>Additional details and impacted files</summary>


```diff
@@           Coverage Diff           @@
##           master   #11566   +/-   ##
=======================================
  Coverage   46.85%   46.85%           
=======================================
  Files         240      240           
  Lines       39950    39950           
=======================================
  Hits        18719    18719           
  Misses      19338    19338           
  Partials     1893     1893           
```


| [Impacted Files](https://codecov.io/gh/argoproj/argo-cd/pull/11566?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=argoproj) | Coverage Δ | |
|---|---|---|
| [pkg/apis/application/v1alpha1/repository\_types.go](https://codecov.io/gh/argoproj/argo-cd/pull/11566/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=argoproj#diff-cGtnL2FwaXMvYXBwbGljYXRpb24vdjFhbHBoYTEvcmVwb3NpdG9yeV90eXBlcy5nbw==) | `70.67% <0.00%> (ø)` | |
| [util/git/creds.go](https://codecov.io/gh/argoproj/argo-cd/pull/11566/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=argoproj#diff-dXRpbC9naXQvY3JlZHMuZ28=) | `41.27% <0.00%> (ø)` | |
| [util/settings/settings.go](https://codecov.io/gh/argoproj/argo-cd/pull/11566/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=argoproj#diff-dXRpbC9zZXR0aW5ncy9zZXR0aW5ncy5nbw==) | `48.87% <0.00%> (ø)` | |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=argoproj). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=argoproj)

</details>

[:umbrella: View full report at Codecov](https://codecov.io/gh/argoproj/argo-cd/pull/11566?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=argoproj).   
:loudspeaker: Do you have feedback about the report comment? [Let us know in this issue](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=argoproj).


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/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/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 24d25ae7e..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
@@ -93,7 +94,7 @@ require (
 	k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9
 	layeh.com/gopher-json v0.0.0-20190114024228-97fed8db8427
 	sigs.k8s.io/controller-runtime v0.11.0
-	sigs.k8s.io/structured-merge-diff/v4 v4.2.1
+	sigs.k8s.io/structured-merge-diff/v4 v4.2.3
 	sigs.k8s.io/yaml v1.3.0
 )
 
@@ -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 af581ffd1..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=
@@ -1887,8 +1896,9 @@ sigs.k8s.io/kustomize/kustomize/v4 v4.5.4/go.mod h1:Zo/Xc5FKD6sHl0lilbrieeGeZHVY
 sigs.k8s.io/kustomize/kyaml v0.13.6 h1:eF+wsn4J7GOAXlvajv6OknSunxpcOBQQqsnPxObtkGs=
 sigs.k8s.io/kustomize/kyaml v0.13.6/go.mod h1:yHP031rn1QX1lr/Xd934Ri/xdVNG8BE2ECa78Ht/kEg=
 sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
-sigs.k8s.io/structured-merge-diff/v4 v4.2.1 h1:bKCqE9GvQ5tiVHn5rfn1r+yao3aLQEaLzkkmAkf+A6Y=
 sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4=
+sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE=
+sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E=
 sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
 sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
 sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
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/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/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/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/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/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
+`
+}