Repository: linkerd/linkerd2
Pull request #6612 (2021-08-05 17:04:26)

Bump @babel/preset-env from 7.14.9 to 7.15.0 in /web/app
Bumps [@babel/preset-env](https://github.com/babel/babel/tree/HEAD/packages/babel-preset-env) from 7.14.9 to 7.15.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/babel/babel/releases"><code>@​babel/preset-env</code>'s releases</a>.</em></p>
<blockquote>
<h2>v7.15.0 (2021-08-04)</h2>
<p>Thanks <a href="https://github.com/a-tarasyuk"><code>@​a-tarasyuk</code></a> and <a href="https://github.com/gausie"><code>@​gausie</code></a> for your first PRs!</p>
<h4>:eyeglasses: Spec Compliance</h4>
<ul>
<li><code>babel-parser</code>
<ul>
<li><a href="https://github-redirect.dependabot.com/babel/babel/pull/13523">#13523</a> feat(ts): raise error for abstract property with initializer (<a href="https://github.com/fedeci"><code>@​fedeci</code></a>)</li>
</ul>
</li>
</ul>
<h4>:rocket: New Feature</h4>
<ul>
<li><code>babel-parser</code>
<ul>
<li><a href="https://github-redirect.dependabot.com/babel/babel/pull/13229">#13229</a> Add <code>attachComment</code> parser option to disable comment attachment (<a href="https://github.com/JLHwung"><code>@​JLHwung</code></a>)</li>
</ul>
</li>
<li><code>babel-standalone</code>
<ul>
<li><a href="https://github-redirect.dependabot.com/babel/babel/pull/13476">#13476</a> standalone: update <code>preset-stage-*</code> (<a href="https://github.com/sosukesuzuki"><code>@​sosukesuzuki</code></a>)</li>
<li><a href="https://github-redirect.dependabot.com/babel/babel/pull/13555">#13555</a> feat: support hack pipeline in <code>@babel/standalone</code> (<a href="https://github.com/JLHwung"><code>@​JLHwung</code></a>)</li>
</ul>
</li>
<li><code>babel-parser</code>, <code>babel-preset-env</code>
<ul>
<li><a href="https://github-redirect.dependabot.com/babel/babel/pull/13387">#13387</a> Enable top-level <code>await</code> parsing by default (<a href="https://github.com/nicolo-ribaudo"><code>@​nicolo-ribaudo</code></a>)</li>
</ul>
</li>
<li><code>babel-plugin-transform-typescript</code>, <code>babel-preset-typescript</code>
<ul>
<li><a href="https://github-redirect.dependabot.com/babel/babel/pull/13324">#13324</a> Support TypeScript const enums (<a href="https://github.com/nicolo-ribaudo"><code>@​nicolo-ribaudo</code></a>)</li>
</ul>
</li>
<li><code>babel-plugin-transform-typescript</code>
<ul>
<li><a href="https://github-redirect.dependabot.com/babel/babel/pull/13528">#13528</a> feat(typescript): implement namespace alias (<a href="https://github.com/colinaaa"><code>@​colinaaa</code></a>)</li>
</ul>
</li>
<li><code>babel-core</code>, <code>babel-helper-create-class-features-plugin</code>, <code>babel-helper-module-transforms</code>, <code>babel-plugin-transform-modules-commonjs</code>
<ul>
<li><a href="https://github-redirect.dependabot.com/babel/babel/pull/13290">#13290</a> feat: add <code>noIncompleteNsImportDetection</code> assumption to <code>plugin-transform-modules-commonjs</code> (<a href="https://github.com/fedeci"><code>@​fedeci</code></a>)</li>
</ul>
</li>
<li><code>babel-plugin-transform-react-display-name</code>
<ul>
<li><a href="https://github-redirect.dependabot.com/babel/babel/pull/13501">#13501</a> Add display name after create context (<a href="https://github.com/JLHwung"><code>@​JLHwung</code></a>)</li>
</ul>
</li>
<li><code>babel-parser</code>, <code>babel-plugin-proposal-pipeline-operator</code>, <code>babel-plugin-syntax-pipeline-operator</code>
<ul>
<li><a href="https://github-redirect.dependabot.com/babel/babel/pull/13416">#13416</a> Hack-pipe proposal with <code>%</code> topic token (<a href="https://github.com/js-choi"><code>@​js-choi</code></a>)</li>
</ul>
</li>
<li><code>babel-generator</code>, <code>babel-parser</code>, <code>babel-plugin-proposal-pipeline-operator</code>, <code>babel-plugin-syntax-pipeline-operator</code>, <code>babel-traverse</code>, <code>babel-types</code>
<ul>
<li><a href="https://github-redirect.dependabot.com/babel/babel/pull/13191">#13191</a> Add support for the &quot;Hack&quot; pipeline proposal (<a href="https://github.com/js-choi"><code>@​js-choi</code></a>)</li>
</ul>
</li>
<li><code>babel-plugin-transform-runtime</code>
<ul>
<li><a href="https://github-redirect.dependabot.com/babel/babel/pull/13398">#13398</a> Expose <code>@babel/eslint-parser/experimental-worker</code> (<a href="https://github.com/nicolo-ribaudo"><code>@​nicolo-ribaudo</code></a>)</li>
</ul>
</li>
<li><code>babel-compat-data</code>, <code>babel-helper-compilation-targets</code>, <code>babel-preset-env</code>
<ul>
<li><a href="https://github-redirect.dependabot.com/babel/babel/pull/13448">#13448</a> Add support for rhino as a compilation target (<a href="https://github.com/gausie"><code>@​gausie</code></a>)</li>
</ul>
</li>
<li><code>babel-compat-data</code>, <code>babel-parser</code>, <code>babel-preset-env</code>
<ul>
<li><a href="https://github-redirect.dependabot.com/babel/babel/pull/13554">#13554</a> Enable ergonomic brand checks (<code>#priv in</code>) by default (<a href="https://github.com/nicolo-ribaudo"><code>@​nicolo-ribaudo</code></a>)</li>
</ul>
</li>
</ul>
<h4>:bug: Bug Fix</h4>
<ul>
<li><code>babel-parser</code>, <code>babel-plugin-transform-typescript</code>
<ul>
<li><a href="https://github-redirect.dependabot.com/babel/babel/pull/13513">#13513</a> [ts] support optional chain call with generic (<a href="https://github.com/lala7573"><code>@​lala7573</code></a>)</li>
</ul>
</li>
<li><code>babel-plugin-transform-typescript</code>
<ul>
<li><a href="https://github-redirect.dependabot.com/babel/babel/pull/13605">#13605</a> Handle typescript function overloading in a default export (<a href="https://github.com/tony-go"><code>@​tony-go</code></a>)</li>
</ul>
</li>
<li><code>babel-parser</code>
<ul>
<li><a href="https://github-redirect.dependabot.com/babel/babel/pull/13536">#13536</a> Fix <code>%==</code> parsing in hack pipes (<a href="https://github.com/nicolo-ribaudo"><code>@​nicolo-ribaudo</code></a>)</li>
<li><a href="https://github-redirect.dependabot.com/babel/babel/pull/13426">#13426</a> parser: Fix Hack/smart-pipe error positions (<a href="https://github.com/js-choi"><code>@​js-choi</code></a>)</li>
<li><a href="https://github-redirect.dependabot.com/babel/babel/pull/13622">#13622</a> fix(ts): raise error for <code>export default interface {}</code> (<a href="https://github.com/a-tarasyuk"><code>@​a-tarasyuk</code></a>)</li>
</ul>
</li>
</ul>
<h4>:memo: Documentation</h4>
<ul>
<li><a href="https://github-redirect.dependabot.com/babel/babel/pull/13607">#13607</a> chore(doc): add jest specific pckg command (<a href="https://github.com/tony-go"><code>@​tony-go</code></a>)</li>
</ul>
<h4>Committers: 10</h4>
<ul>
<li>Federico Ciardi (<a href="https://github.com/fedeci"><code>@​fedeci</code></a>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a href="https://github.com/babel/babel/blob/main/CHANGELOG.md"><code>@​babel/preset-env</code>'s changelog</a>.</em></p>
<blockquote>
<h2>v7.15.0 (2021-08-04)</h2>
<h4>:eyeglasses: Spec Compliance</h4>
<ul>
<li><code>babel-parser</code>
<ul>
<li><a href="https://github-redirect.dependabot.com/babel/babel/pull/13523">#13523</a> feat(ts): raise error for abstract property with initializer (<a href="https://github.com/fedeci"><code>@​fedeci</code></a>)</li>
</ul>
</li>
</ul>
<h4>:rocket: New Feature</h4>
<ul>
<li><code>babel-parser</code>
<ul>
<li><a href="https://github-redirect.dependabot.com/babel/babel/pull/13229">#13229</a> Add <code>attachComment</code> parser option to disable comment attachment (<a href="https://github.com/JLHwung"><code>@​JLHwung</code></a>)</li>
</ul>
</li>
<li><code>babel-standalone</code>
<ul>
<li><a href="https://github-redirect.dependabot.com/babel/babel/pull/13476">#13476</a> standalone: update <code>preset-stage-*</code> (<a href="https://github.com/sosukesuzuki"><code>@​sosukesuzuki</code></a>)</li>
<li><a href="https://github-redirect.dependabot.com/babel/babel/pull/13555">#13555</a> feat: support hack pipeline in <code>@babel/standalone</code> (<a href="https://github.com/JLHwung"><code>@​JLHwung</code></a>)</li>
</ul>
</li>
<li><code>babel-parser</code>, <code>babel-preset-env</code>
<ul>
<li><a href="https://github-redirect.dependabot.com/babel/babel/pull/13387">#13387</a> Enable top-level <code>await</code> parsing by default (<a href="https://github.com/nicolo-ribaudo"><code>@​nicolo-ribaudo</code></a>)</li>
</ul>
</li>
<li><code>babel-plugin-transform-typescript</code>, <code>babel-preset-typescript</code>
<ul>
<li><a href="https://github-redirect.dependabot.com/babel/babel/pull/13324">#13324</a> Support TypeScript const enums (<a href="https://github.com/nicolo-ribaudo"><code>@​nicolo-ribaudo</code></a>)</li>
</ul>
</li>
<li><code>babel-plugin-transform-typescript</code>
<ul>
<li><a href="https://github-redirect.dependabot.com/babel/babel/pull/13528">#13528</a> feat(typescript): implement namespace alias (<a href="https://github.com/colinaaa"><code>@​colinaaa</code></a>)</li>
</ul>
</li>
<li><code>babel-core</code>, <code>babel-helper-create-class-features-plugin</code>, <code>babel-helper-module-transforms</code>, <code>babel-plugin-transform-modules-commonjs</code>
<ul>
<li><a href="https://github-redirect.dependabot.com/babel/babel/pull/13290">#13290</a> feat: add <code>noIncompleteNsImportDetection</code> assumption to <code>plugin-transform-modules-commonjs</code> (<a href="https://github.com/fedeci"><code>@​fedeci</code></a>)</li>
</ul>
</li>
<li><code>babel-plugin-transform-react-display-name</code>
<ul>
<li><a href="https://github-redirect.dependabot.com/babel/babel/pull/13501">#13501</a> Add display name after create context (<a href="https://github.com/JLHwung"><code>@​JLHwung</code></a>)</li>
</ul>
</li>
<li><code>babel-parser</code>, <code>babel-plugin-proposal-pipeline-operator</code>, <code>babel-plugin-syntax-pipeline-operator</code>
<ul>
<li><a href="https://github-redirect.dependabot.com/babel/babel/pull/13416">#13416</a> Hack-pipe proposal with <code>%</code> topic token (<a href="https://github.com/js-choi"><code>@​js-choi</code></a>)</li>
</ul>
</li>
<li><code>babel-generator</code>, <code>babel-parser</code>, <code>babel-plugin-proposal-pipeline-operator</code>, <code>babel-plugin-syntax-pipeline-operator</code>, <code>babel-traverse</code>, <code>babel-types</code>
<ul>
<li><a href="https://github-redirect.dependabot.com/babel/babel/pull/13191">#13191</a> Add support for the &quot;Hack&quot; pipeline proposal (<a href="https://github.com/js-choi"><code>@​js-choi</code></a>)</li>
</ul>
</li>
<li><code>babel-plugin-transform-runtime</code>
<ul>
<li><a href="https://github-redirect.dependabot.com/babel/babel/pull/13398">#13398</a> Expose <code>@babel/eslint-parser/experimental-worker</code> (<a href="https://github.com/nicolo-ribaudo"><code>@​nicolo-ribaudo</code></a>)</li>
</ul>
</li>
<li><code>babel-compat-data</code>, <code>babel-helper-compilation-targets</code>, <code>babel-preset-env</code>
<ul>
<li><a href="https://github-redirect.dependabot.com/babel/babel/pull/13448">#13448</a> Add support for rhino as a compilation target (<a href="https://github.com/gausie"><code>@​gausie</code></a>)</li>
</ul>
</li>
<li><code>babel-compat-data</code>, <code>babel-parser</code>, <code>babel-preset-env</code>
<ul>
<li><a href="https://github-redirect.dependabot.com/babel/babel/pull/13554">#13554</a> Enable ergonomic brand checks (<code>#priv in</code>) by default (<a href="https://github.com/nicolo-ribaudo"><code>@​nicolo-ribaudo</code></a>)</li>
</ul>
</li>
</ul>
<h4>:bug: Bug Fix</h4>
<ul>
<li><code>babel-parser</code>, <code>babel-plugin-transform-typescript</code>
<ul>
<li><a href="https://github-redirect.dependabot.com/babel/babel/pull/13513">#13513</a> [ts] support optional chain call with generic (<a href="https://github.com/lala7573"><code>@​lala7573</code></a>)</li>
</ul>
</li>
<li><code>babel-plugin-transform-typescript</code>
<ul>
<li><a href="https://github-redirect.dependabot.com/babel/babel/pull/13605">#13605</a> Handle typescript function overloading in a default export (<a href="https://github.com/tony-go"><code>@​tony-go</code></a>)</li>
</ul>
</li>
<li><code>babel-parser</code>
<ul>
<li><a href="https://github-redirect.dependabot.com/babel/babel/pull/13536">#13536</a> Fix <code>%==</code> parsing in hack pipes (<a href="https://github.com/nicolo-ribaudo"><code>@​nicolo-ribaudo</code></a>)</li>
<li><a href="https://github-redirect.dependabot.com/babel/babel/pull/13426">#13426</a> parser: Fix Hack/smart-pipe error positions (<a href="https://github.com/js-choi"><code>@​js-choi</code></a>)</li>
<li><a href="https://github-redirect.dependabot.com/babel/babel/pull/13622">#13622</a> fix(ts): raise error for <code>export default interface {}</code> (<a href="https://github.com/a-tarasyuk"><code>@​a-tarasyuk</code></a>)</li>
</ul>
</li>
</ul>
<h4>:memo: Documentation</h4>
<ul>
<li><a href="https://github-redirect.dependabot.com/babel/babel/pull/13607">#13607</a> chore(doc): add jest specific pckg command (<a href="https://github.com/tony-go"><code>@​tony-go</code></a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/babel/babel/commit/6912f968a6445248f97b1a2e8c86220fa8fdf25d"><code>6912f96</code></a> v7.15.0</li>
<li><a href="https://github.com/babel/babel/commit/ddaf0d4296543789e602a7762a360be179b3e154"><code>ddaf0d4</code></a> Enable top-level <code>await</code> parsing by default (<a href="https://github.com/babel/babel/tree/HEAD/packages/babel-preset-env/issues/13387">#13387</a>)</li>
<li><a href="https://github.com/babel/babel/commit/830b99dc83283751a4cac8c4e1248e56e35c5e96"><code>830b99d</code></a> Add support for rhino as a compilation target (<a href="https://github.com/babel/babel/tree/HEAD/packages/babel-preset-env/issues/13448">#13448</a>)</li>
<li><a href="https://github.com/babel/babel/commit/a254ea38a4e9ea3c6514d7548ce105f8557936f7"><code>a254ea3</code></a> Enable ergonomic brand checks (<code>#priv in</code>) by default (<a href="https://github.com/babel/babel/tree/HEAD/packages/babel-preset-env/issues/13554">#13554</a>)</li>
<li>See full diff in <a href="https://github.com/babel/babel/commits/v7.15.0/packages/babel-preset-env">compare view</a></li>
</ul>
</details>
<br />


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

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

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

---

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

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>

codecov-commenter: # [Codecov](https://codecov.io/gh/linkerd/linkerd2/pull/6612?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=linkerd) Report
> Merging [#6612](https://codecov.io/gh/linkerd/linkerd2/pull/6612?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=linkerd) (19e8b32) into [main](https://codecov.io/gh/linkerd/linkerd2/commit/a7806ec1ce7f14d785b1b9c5ec6280e6f7781c1d?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=linkerd) (a7806ec) will **increase** coverage by `22.67%`.
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/linkerd/linkerd2/pull/6612/graphs/tree.svg?width=650&height=150&src=pr&token=FCBISvpqTv&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=linkerd)](https://codecov.io/gh/linkerd/linkerd2/pull/6612?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=linkerd)

```diff
@@             Coverage Diff             @@
##             main    #6612       +/-   ##
===========================================
+ Coverage   44.09%   66.76%   +22.67%     
===========================================
  Files         143       39      -104     
  Lines       22968     1396    -21572     
  Branches        0      290      +290     
===========================================
- Hits        10127      932     -9195     
+ Misses      12065      460    -11605     
+ Partials      776        4      -772     
```

| Flag | Coverage Δ | |
|---|---|---|
| golang | `?` | |
| javascript | `66.76% <ø> (?)` | |
| unittests | `66.76% <ø> (+22.67%)` | :arrow_up: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=linkerd#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Impacted Files](https://codecov.io/gh/linkerd/linkerd2/pull/6612?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=linkerd) | Coverage Δ | |
|---|---|---|
| [testutil/stream.go](https://codecov.io/gh/linkerd/linkerd2/pull/6612/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=linkerd#diff-dGVzdHV0aWwvc3RyZWFtLmdv) | | |
| [jaeger/cmd/uninstall.go](https://codecov.io/gh/linkerd/linkerd2/pull/6612/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=linkerd#diff-amFlZ2VyL2NtZC91bmluc3RhbGwuZ28=) | | |
| [pkg/tls/cred.go](https://codecov.io/gh/linkerd/linkerd2/pull/6612/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=linkerd#diff-cGtnL3Rscy9jcmVkLmdv) | | |
| [pkg/inject/uninject.go](https://codecov.io/gh/linkerd/linkerd2/pull/6612/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=linkerd#diff-cGtnL2luamVjdC91bmluamVjdC5nbw==) | | |
| [controller/k8s/test\_helper.go](https://codecov.io/gh/linkerd/linkerd2/pull/6612/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=linkerd#diff-Y29udHJvbGxlci9rOHMvdGVzdF9oZWxwZXIuZ28=) | | |
| [cli/cmd/diagnostics.go](https://codecov.io/gh/linkerd/linkerd2/pull/6612/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=linkerd#diff-Y2xpL2NtZC9kaWFnbm9zdGljcy5nbw==) | | |
| [pkg/version/version.go](https://codecov.io/gh/linkerd/linkerd2/pull/6612/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=linkerd#diff-cGtnL3ZlcnNpb24vdmVyc2lvbi5nbw==) | | |
| [viz/cmd/dashboard.go](https://codecov.io/gh/linkerd/linkerd2/pull/6612/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=linkerd#diff-dml6L2NtZC9kYXNoYm9hcmQuZ28=) | | |
| [controller/webhook/server.go](https://codecov.io/gh/linkerd/linkerd2/pull/6612/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=linkerd#diff-Y29udHJvbGxlci93ZWJob29rL3NlcnZlci5nbw==) | | |
| [controller/api/destination/client.go](https://codecov.io/gh/linkerd/linkerd2/pull/6612/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=linkerd#diff-Y29udHJvbGxlci9hcGkvZGVzdGluYXRpb24vY2xpZW50Lmdv) | | |
| ... and [172 more](https://codecov.io/gh/linkerd/linkerd2/pull/6612/diff?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=linkerd) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/linkerd/linkerd2/pull/6612?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=linkerd).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=linkerd)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/linkerd/linkerd2/pull/6612?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=linkerd). Last update [a7806ec...19e8b32](https://codecov.io/gh/linkerd/linkerd2/pull/6612?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=linkerd). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=linkerd).


Patch:
diff --git a/.github/workflows/lock.yml b/.github/workflows/lock.yml
index c4617d852..d6caf4e61 100644
--- a/.github/workflows/lock.yml
+++ b/.github/workflows/lock.yml
@@ -2,7 +2,7 @@ name: 'Lock Threads'
 
 on:
   schedule:
-    - cron: '0 * * * *'
+    - cron: '0 1 * * *'
 
 permissions:
   issues: write
diff --git a/CHANGES.md b/CHANGES.md
index ada6b6be5..9d1536665 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,5 +1,18 @@
 # Changes
 
+## edge-21.8.1
+
+This releases includes initial changes w.r.t addition of Authorization into
+Linkerd. It includes adding the new `policy.linkerd.io` CRDs to the core install.
+This also includes numerous dependency updates both in the web and dashboard.
+
+* Added `servers.policy.linkerd.io` and `serverauthorizations.policy.linkerd.io`
+  CRDs into the default Linkerd installation to support configuration and
+  discovery of inbound policies
+* Modified the proxy to support upcoming policy features
+* Updated several dashboard dependencies to latest versions
+* Updated several proxy dependencies to latest versions
+
 ## edge-21.7.5
 
 This release updates Linkerd to store the identity trust root in a ConfigMap to
diff --git a/cli/cmd/identity.go b/cli/cmd/identity.go
index 3b4724efd..6281c2850 100644
--- a/cli/cmd/identity.go
+++ b/cli/cmd/identity.go
@@ -191,7 +191,7 @@ func getContainerCertificate(k8sAPI *k8s.KubernetesAPI, pod corev1.Pod, containe
 }
 
 func getCertResponse(url string, pod corev1.Pod) ([]*x509.Certificate, error) {
-	serverName, err := getServerName(pod, k8s.ProxyContainerName)
+	serverName, err := k8s.PodIdentity(&pod)
 	if err != nil {
 		return nil, err
 	}
@@ -209,32 +209,6 @@ func getCertResponse(url string, pod corev1.Pod) ([]*x509.Certificate, error) {
 	return cert, nil
 }
 
-func getServerName(pod corev1.Pod, containerName string) (string, error) {
-	if pod.Status.Phase != corev1.PodRunning {
-		return "", fmt.Errorf("pod not running: %s", pod.GetName())
-	}
-
-	var l5dns string
-	var l5dtrustdomain string
-	podsa := pod.Spec.ServiceAccountName
-	podns := pod.ObjectMeta.Namespace
-	for _, c := range pod.Spec.Containers {
-		if c.Name == containerName {
-			for _, env := range c.Env {
-				if env.Name == "_l5d_ns" {
-					l5dns = env.Value
-				}
-				if env.Name == "_l5d_trustdomain" {
-					l5dtrustdomain = env.Value
-				}
-			}
-		}
-	}
-
-	serverName := fmt.Sprintf("%s.%s.serviceaccount.identity.%s.%s", podsa, podns, l5dns, l5dtrustdomain)
-	return serverName, nil
-}
-
 func getPods(ctx context.Context, clientset kubernetes.Interface, namespace string, selector string, args []string) ([]corev1.Pod, error) {
 	if len(args) > 0 {
 		var pods []corev1.Pod
diff --git a/pkg/k8s/k8s.go b/pkg/k8s/k8s.go
index f64f77693..a057409d6 100644
--- a/pkg/k8s/k8s.go
+++ b/pkg/k8s/k8s.go
@@ -3,6 +3,7 @@ package k8s
 import (
 	"fmt"
 
+	corev1 "k8s.io/api/core/v1"
 	"k8s.io/client-go/rest"
 	"k8s.io/client-go/tools/clientcmd"
 )
@@ -193,3 +194,30 @@ func KindToL5DLabel(k8sKind string) string {
 	}
 	return k8sKind
 }
+
+// PodIdentity returns the mesh TLS identity name of this pod, as constructed
+// from the pod's service account name and other metadata.
+func PodIdentity(pod *corev1.Pod) (string, error) {
+	if pod.Status.Phase != corev1.PodRunning {
+		return "", fmt.Errorf("pod not running: %s", pod.GetName())
+	}
+
+	podsa := pod.Spec.ServiceAccountName
+	podns := pod.ObjectMeta.Namespace
+	for _, c := range pod.Spec.Containers {
+		if c.Name == ProxyContainerName {
+			var l5dns string
+			var l5dtrustdomain string
+			for _, env := range c.Env {
+				if env.Name == "_l5d_ns" {
+					l5dns = env.Value
+				}
+				if env.Name == "_l5d_trustdomain" {
+					l5dtrustdomain = env.Value
+				}
+			}
+			return fmt.Sprintf("%s.%s.serviceaccount.identity.%s.%s", podsa, podns, l5dns, l5dtrustdomain), nil
+		}
+	}
+	return "", nil
+}
diff --git a/viz/metrics-api/edges.go b/viz/metrics-api/edges.go
index 1f4627ce1..7faa1fbb9 100644
--- a/viz/metrics-api/edges.go
+++ b/viz/metrics-api/edges.go
@@ -2,19 +2,18 @@ package api
 
 import (
 	"context"
-	"errors"
 	"fmt"
 	"sort"
-	"strings"
 
+	"github.com/linkerd/linkerd2/pkg/k8s"
 	pb "github.com/linkerd/linkerd2/viz/metrics-api/gen/viz"
 	"github.com/prometheus/common/model"
 	log "github.com/sirupsen/logrus"
+	v1 "k8s.io/api/core/v1"
 )
 
 const (
-	inboundIdentityQuery  = "count(%s%s) by (%s, client_id, namespace, no_tls_reason)"
-	outboundIdentityQuery = "count(%s%s) by (%s, dst_%s, server_id, namespace, dst_namespace, no_tls_reason)"
+	edgesQuery = "sum(%s%s) by (%s, dst_%s, pod, server_id, namespace, dst_namespace, no_tls_reason)"
 )
 
 var formatMsg = map[string]string{
@@ -26,48 +25,78 @@ var formatMsg = map[string]string{
 	"not_provided_by_service_discovery": "Not Provided By Service Discovery",
 }
 
+type edgeKey struct {
+	src   string
+	srcNs string
+	dst   string
+	dstNs string
+}
+
 func (s *grpcServer) Edges(ctx context.Context, req *pb.EdgesRequest) (*pb.EdgesResponse, error) {
 	log.Debugf("Edges request: %+v", req)
 	if req.GetSelector().GetResource() == nil {
 		return edgesError(req, "Edges request missing Selector Resource"), nil
 	}
 
-	edgesHTTP, err := s.getEdges(ctx, req, "response_total")
-	if err != nil {
-		return edgesError(req, err.Error()), nil
-	}
-	edgesTCP, err := s.getEdges(ctx, req, "tcp_open_total")
+	resourceType := promResourceType(req.GetSelector().GetResource())
+	dstResourceType := "dst_" + resourceType
+	labelsOutbound := promDirectionLabels("outbound")
+	labelsOutboundStr := generateLabelStringWithExclusion(labelsOutbound, string(resourceType), string(dstResourceType))
+	query := fmt.Sprintf(edgesQuery, "tcp_open_connections", labelsOutboundStr, resourceType, resourceType)
+
+	promResult, err := s.queryProm(ctx, query)
 	if err != nil {
 		return edgesError(req, err.Error()), nil
 	}
 
-	edges := []*pb.Edge{}
-	// iterate over tcp_open_total metrics
-	for _, edgeTCP := range edgesTCP {
-		edgeHTTPIndex := -1
-		// find the corresponding response_total entry
-		for i, edgeHTTP := range edgesHTTP {
-			if equalEdges(edgeTCP, edgeHTTP) {
-				edgeHTTPIndex = i
-				break
+	edgeMap := make(map[edgeKey]*pb.Edge)
+
+	for _, sample := range promResult {
+		if sample.Value == 0.0 {
+			continue
+		}
+		key := edgeKey{
+			src:   string(sample.Metric[resourceType]),
+			srcNs: string(sample.Metric[model.LabelName("namespace")]),
+			dst:   string(sample.Metric[dstResourceType]),
+			dstNs: string(sample.Metric[model.LabelName("dst_namespace")]),
+		}
+		requestedNs := req.GetSelector().GetResource().GetNamespace()
+		if requestedNs != v1.NamespaceAll {
+			if requestedNs != key.srcNs && requestedNs != key.dstNs {
+				continue
 			}
 		}
+		if _, ok := edgeMap[key]; !ok {
 
-		edge := edgeTCP
-		if edgeHTTPIndex > -1 {
-			// if tcp_open_total doesn't have client_id info,
-			// use the response_total metric instead
-			if edge.ClientId == "" {
-				edge = edgesHTTP[edgeHTTPIndex]
+			clientID, err := s.getPodIdentity(string(sample.Metric[model.LabelName("pod")]), key.srcNs)
+			if err != nil {
+				log.Warnf("failed to get pod identity for %s: %v", sample.Metric[model.LabelName("pod")], err)
+				continue
+			}
+
+			edgeMap[key] = &pb.Edge{
+				Src: &pb.Resource{
+					Namespace: key.srcNs,
+					Name:      key.src,
+					Type:      string(resourceType),
+				},
+				Dst: &pb.Resource{
+					Namespace: key.dstNs,
+					Name:      key.dst,
+					Type:      string(resourceType),
+				},
+				ServerId:      string(sample.Metric[model.LabelName("server_id")]),
+				ClientId:      clientID,
+				NoIdentityMsg: formatMsg[string(sample.Metric[model.LabelName("no_tls_reason")])],
 			}
-			// trick to remove element quickly
-			edgesHTTP[edgeHTTPIndex] = edgesHTTP[len(edgesHTTP)-1]
-			edgesHTTP = edgesHTTP[:len(edgesHTTP)-1]
 		}
-		edges = append(edges, edge)
 	}
 
-	edges = append(edges, edgesHTTP...)
+	edges := []*pb.Edge{}
+	for _, edge := range edgeMap {
+		edges = append(edges, edge)
+	}
 	edges = sortEdgeRows(edges)
 
 	return &pb.EdgesResponse{
@@ -90,144 +119,12 @@ func edgesError(req *pb.EdgesRequest, message string) *pb.EdgesResponse {
 	}
 }
 
-func (s *grpcServer) getEdges(ctx context.Context, req *pb.EdgesRequest, metric string) ([]*pb.Edge, error) {
-	labelNames := promGroupByLabelNames(req.Selector.Resource)
-	if len(labelNames) != 2 {
-		return nil, errors.New("unexpected resource selector")
-	}
-	selectedNamespace := req.Selector.Resource.Namespace
-	resourceType := string(labelNames[1]) // skipping first name which is always namespace
-	labelsOutbound := promDirectionLabels("outbound")
-	labelsInbound := promDirectionLabels("inbound")
-
-	// checking that data for the specified resource type exists
-	labelsOutboundStr := generateLabelStringWithExclusion(labelsOutbound, resourceType)
-	labelsInboundStr := generateLabelStringWithExclusion(labelsInbound, resourceType)
-
-	outboundQuery := fmt.Sprintf(outboundIdentityQuery, metric, labelsOutboundStr, resourceType, resourceType)
-	inboundQuery := fmt.Sprintf(inboundIdentityQuery, metric, labelsInboundStr, resourceType)
-
-	inboundResult, err := s.queryProm(ctx, inboundQuery)
-	if err != nil {
-		return nil, err
-	}
-
-	outboundResult, err := s.queryProm(ctx, outboundQuery)
+func (s *grpcServer) getPodIdentity(pod string, namespace string) (string, error) {
+	po, err := s.k8sAPI.Pod().Lister().Pods(namespace).Get(pod)
 	if err != nil {
-		return nil, err
+		return "", err
 	}
-
-	edges := processEdgeMetrics(inboundResult, outboundResult, resourceType, selectedNamespace)
-	return edges, nil
-}
-
-func processEdgeMetrics(inbound, outbound model.Vector, resourceType, selectedNamespace string) []*pb.Edge {
-	edges := []*pb.Edge{}
-	dstIndex := map[model.LabelValue]model.Metric{}
-	srcIndex := map[model.LabelValue][]model.Metric{}
-	resourceReplacementInbound := resourceType
-	resourceReplacementOutbound := "dst_" + resourceType
-
-	for _, sample := range inbound {
-		// skip inbound results without a clientID because we cannot construct edge
-		// information
-		if clientID, ok := sample.Metric[model.LabelName("client_id")]; ok {
-			dstResource := string(sample.Metric[model.LabelName(resourceReplacementInbound)])
-
-			// format of clientId is id.namespace.serviceaccount.cluster.local
-			clientIDSlice := strings.Split(string(clientID), ".")
-			srcNs := clientIDSlice[1]
-			key := model.LabelValue(fmt.Sprintf("%s.%s", dstResource, srcNs))
-			dstIndex[key] = sample.Metric
-		}
-	}
-
-	for _, sample := range outbound {
-		// e.g. tcp_open_total from older proxies don't have enough labels
-		if _, ok := sample.Metric[model.LabelName("dst_namespace")]; !ok {
-			continue
-		}
-		dstResource := sample.Metric[model.LabelName(resourceReplacementOutbound)]
-		srcNs := sample.Metric[model.LabelName("namespace")]
-
-		key := model.LabelValue(fmt.Sprintf("%s.%s", dstResource, srcNs))
-		if _, ok := srcIndex[key]; !ok {
-			srcIndex[key] = []model.Metric{}
-		}
-		srcIndex[key] = append(srcIndex[key], sample.Metric)
-	}
-
-	for key, sources := range srcIndex {
-		for _, src := range sources {
-			srcNamespace := string(src[model.LabelName("namespace")])
-
-			dst, ok := dstIndex[key]
-
-			// if no destination, build edge entirely from source data
-			if !ok {
-				dstNamespace := string(src[model.LabelName("dst_namespace")])
-
-				// skip if selected namespace is given and neither the source nor
-				// destination is in the selected namespace
-				if selectedNamespace != "" && srcNamespace != selectedNamespace &&
-					dstNamespace != selectedNamespace {
-					continue
-				}
-
-				srcResource := string(src[model.LabelName(resourceType)])
-				dstResource := string(src[model.LabelName(resourceReplacementOutbound)])
-
-				// skip if source or destination resource is not present
-				if srcResource == "" || dstResource == "" {
-					continue
-				}
-
-				msg := formatMsg[string(src[model.LabelName("no_tls_reason")])]
-				edge := &pb.Edge{
-					Src: &pb.Resource{
-						Namespace: srcNamespace,
-						Name:      srcResource,
-						Type:      resourceType,
-					},
-					Dst: &pb.Resource{
-						Namespace: dstNamespace,
-						Name:      dstResource,
-						Type:      resourceType,
-					},
-					NoIdentityMsg: msg,
-				}
-				edges = append(edges, edge)
-				continue
-			}
-
-			dstNamespace := string(dst[model.LabelName("namespace")])
-
-			// skip if selected namespace is given and neither the source nor
-			// destination is in the selected namespace
-			if selectedNamespace != "" && srcNamespace != selectedNamespace &&
-				dstNamespace != selectedNamespace {
-				continue
-			}
-
-			edge := &pb.Edge{
-				Src: &pb.Resource{
-					Namespace: srcNamespace,
-					Name:      string(src[model.LabelName(resourceType)]),
-					Type:      resourceType,
-				},
-				Dst: &pb.Resource{
-					Namespace: dstNamespace,
-					Name:      string(dst[model.LabelName(resourceType)]),
-					Type:      resourceType,
-				},
-				ClientId: string(dst[model.LabelName("client_id")]),
-				ServerId: string(src[model.LabelName("server_id")]),
-			}
-			edges = append(edges, edge)
-		}
-	}
-
-	return edges
+	return k8s.PodIdentity(po)
 }
 
 func sortEdgeRows(rows []*pb.Edge) []*pb.Edge {
@@ -238,8 +135,3 @@ func sortEdgeRows(rows []*pb.Edge) []*pb.Edge {
 	})
 	return rows
 }
-
-func equalEdges(a, b *pb.Edge) bool {
-	return a.Src.Namespace == b.Src.Namespace && a.Src.Name == b.Src.Name && a.Src.Type == b.Src.Type &&
-		a.Dst.Namespace == b.Dst.Namespace && a.Dst.Name == b.Dst.Name && a.Dst.Type == b.Dst.Type
-}
diff --git a/viz/metrics-api/prometheus.go b/viz/metrics-api/prometheus.go
index c7c1269a4..bdacb3f97 100644
--- a/viz/metrics-api/prometheus.go
+++ b/viz/metrics-api/prometheus.go
@@ -143,12 +143,14 @@ func promDstQueryLabels(resource *pb.Resource) model.LabelSet {
 // insert a not-nil check into a LabelSet to verify that data for a specified
 // label name exists. due to the `!=` this must be inserted as a string. the
 // structure of this code is taken from the Prometheus labelset.go library.
-func generateLabelStringWithExclusion(l model.LabelSet, labelName string) string {
+func generateLabelStringWithExclusion(l model.LabelSet, labelNames ...string) string {
 	lstrs := make([]string, 0, len(l))
 	for l, v := range l {
 		lstrs = append(lstrs, fmt.Sprintf("%s=%q", l, v))
 	}
-	lstrs = append(lstrs, fmt.Sprintf(`%s!=""`, labelName))
+	for _, labelName := range labelNames {
+		lstrs = append(lstrs, fmt.Sprintf(`%s!=""`, labelName))
+	}
 
 	sort.Strings(lstrs)
 	return fmt.Sprintf("{%s}", strings.Join(lstrs, ", "))
diff --git a/web/app/js/components/BreadcrumbHeader.jsx b/web/app/js/components/BreadcrumbHeader.jsx
index 09b70defd..a3381503f 100644
--- a/web/app/js/components/BreadcrumbHeader.jsx
+++ b/web/app/js/components/BreadcrumbHeader.jsx
@@ -15,6 +15,7 @@ const routeToCrumbTitle = {
   routes: <Trans>menuItemRoutes</Trans>,
   community: <Trans>menuItemCommunity</Trans>,
   gateways: <Trans>menuItemGateway</Trans>,
+  extensions: <Trans>menuItemExtension</Trans>,
 };
 
 class BreadcrumbHeader extends React.Component {
diff --git a/web/app/js/components/Extensions.jsx b/web/app/js/components/Extensions.jsx
new file mode 100644
index 000000000..f449211a3
--- /dev/null
+++ b/web/app/js/components/Extensions.jsx
@@ -0,0 +1,99 @@
+import 'whatwg-fetch';
+
+import React from 'react';
+import ErrorBanner from './ErrorBanner.jsx';
+import Button from '@material-ui/core/Button';
+import Paper from '@material-ui/core/Paper';
+import Grid from '@material-ui/core/Grid';
+import { withStyles } from '@material-ui/core/styles';
+import Typography from '@material-ui/core/Typography';
+import { Trans } from '@lingui/macro';
+
+const styles = theme => ({
+  extensionCard: {
+    padding: theme.spacing(2),
+    maxWidth: '1200px',
+  },
+  screenshot: {
+    margin: 'auto',
+    display: 'block',
+    height: '100%',
+    width: '100%',
+    borderRadius: '8px',
+  },
+});
+
+class Extensions extends React.Component {
+  constructor(props) {
+    super(props);
+    this.loadFromServer = this.loadFromServer.bind(this);
+    this.state = {
+      extensions: [],
+    };
+  }
+
+  componentDidMount() {
+    this.loadFromServer();
+  }
+
+  loadFromServer() {
+    fetch('https://linkerd.io/extensions/index.json', { cache: 'no-store' })
+      .then(res => res.json())
+      .then(json => {
+        this.setState({ extensions: json.data });
+      }).catch(err => this.handleApiError(err));
+  }
+
+  handleApiError = e => {
+    if (e.isCanceled) {
+      return;
+    }
+    this.setState({
+      error: e,
+    });
+  };
+
+  render() {
+    const { classes } = this.props;
+    const { extensions, error } = this.state;
+
+    if (error) {
+      return <ErrorBanner message={error} onHideMessage={() => this.setState({ error: null })} />;
+    }
+
+    return (
+      <Grid container direction="column" spacing={3} justifyContent="center" alignItems="center">
+        <Grid item xs={12}>
+          <Typography variant="h6">
+            <Trans>extensionsPageMsg</Trans>
+          </Typography>
+        </Grid>
+        {extensions.map(ext => (
+          <Grid key={ext.name} item xs={12}>
+            <Paper elevation={3} className={classes.extensionCard}>
+              <Grid container spacing={1} direction="row" justifyContent="space-between" alignItems="center">
+                <Grid container spacing={3} item xs={8} direction="column" justifyContent="center">
+                  <Grid item><Typography variant="h5">{ext.name}</Typography></Grid>
+                  <Grid item><Typography variant="body1">{ext.description}</Typography></Grid>
+                  <Grid item>
+                    <Button
+                      color="primary"
+                      href={ext.docLink}
+                      target="_blank">
+                      <Trans>buttonLearnMore</Trans>
+                    </Button>
+                  </Grid>
+                </Grid>
+                <Grid item xs={4}>
+                  <img className={classes.screenshot} src={ext.screenshotURL} alt={`${ext.name} extension screenshot`} />
+                </Grid>
+              </Grid>
+            </Paper>
+          </Grid>
+        ))}
+      </Grid>
+    );
+  }
+}
+
+export default withStyles(styles)(Extensions);
diff --git a/web/app/js/components/Navigation.jsx b/web/app/js/components/Navigation.jsx
index 44f74783f..85ecc7fab 100644
--- a/web/app/js/components/Navigation.jsx
+++ b/web/app/js/components/Navigation.jsx
@@ -36,6 +36,7 @@ import { faMicroscope } from '@fortawesome/free-solid-svg-icons/faMicroscope';
 import { faRandom } from '@fortawesome/free-solid-svg-icons/faRandom';
 import { faSmile } from '@fortawesome/free-regular-svg-icons/faSmile';
 import { faStream } from '@fortawesome/free-solid-svg-icons/faStream';
+import { faPuzzlePiece } from '@fortawesome/free-solid-svg-icons/faPuzzlePiece';
 import grey from '@material-ui/core/colors/grey';
 import { processSingleResourceRollup } from './util/MetricUtils.jsx';
 import { regexFilterString } from './util/Utils.js';
@@ -373,7 +374,8 @@ class NavigationBase extends React.Component {
 
   handleFilterInputChange = event => {
     this.setState({
-      formattedNamespaceFilter: regexFilterString(event.target.value) });
+      formattedNamespaceFilter: regexFilterString(event.target.value),
+    });
   }
 
   handleAutocompleteClick = event => {
@@ -455,11 +457,11 @@ class NavigationBase extends React.Component {
     const drawer = (
       <div>
         { !mobileSidebarOpen &&
-        <div className={classes.navToolbar}>
-          <div className={classes.linkerdNavLogo}>
-            <Link to="/namespaces">{linkerdWordLogo}</Link>
+          <div className={classes.navToolbar}>
+            <div className={classes.linkerdNavLogo}>
+              <Link to="/namespaces">{linkerdWordLogo}</Link>
+            </div>
           </div>
-        </div>
         }
         <Divider />
         <MenuList>
@@ -562,6 +564,10 @@ class NavigationBase extends React.Component {
               <FontAwesomeIcon icon={faSmile} className={classes.shrinkIcon} />
             </Badge>, this.handleCommunityClick) }
 
+          { this.menuItem('/extensions', <Trans>menuItemExtension</Trans>,
+            <Badge classes={{ badge: classes.badge }}><FontAwesomeIcon icon={faPuzzlePiece} className={classes.shrinkIcon} /></Badge>)
+          }
+
           <MenuItem component="a" href="https://linkerd.io/2/overview/" target="_blank" className={classes.navMenuItem}>
             <ListItemIcon><LibraryBooksIcon className={classes.shrinkIcon} /></ListItemIcon>
             <ListItemText primary={<Trans>menuItemDocumentation</Trans>} />
@@ -624,11 +630,11 @@ class NavigationBase extends React.Component {
                 {linkerdWordLogo}
               </div>
               { !mobileSidebarOpen && // mobile view but no sidebar
-              <React.Fragment>
-                <IconButton onClick={this.handleDrawerClick} className={classes.bars}>
-                  <FontAwesomeIcon icon={faBars} />
-                </IconButton>
-              </React.Fragment>
+                <React.Fragment>
+                  <IconButton onClick={this.handleDrawerClick} className={classes.bars}>
+                    <FontAwesomeIcon icon={faBars} />
+                  </IconButton>
+                </React.Fragment>
               }
             </Toolbar>
           </AppBar>
diff --git a/web/app/js/index.js b/web/app/js/index.js
index e77104efc..7aad7f336 100644
--- a/web/app/js/index.js
+++ b/web/app/js/index.js
@@ -3,12 +3,13 @@ import '../img/favicon.png'; // needs to be referenced somewhere so webpack bund
 
 import { BrowserRouter, Redirect, Route, Switch } from 'react-router-dom';
 import { DETECTORS, LocaleResolver, TRANSFORMERS } from 'locales-detector';
-import { MuiThemeProvider, createMuiTheme } from '@material-ui/core/styles';
+import { MuiThemeProvider, createTheme } from '@material-ui/core/styles';
 
 import ApiHelpers from './components/util/ApiHelpers.jsx';
 import AppContext from './components/util/AppContext.jsx';
 import Community from './components/Community.jsx';
 import CssBaseline from '@material-ui/core/CssBaseline';
+import Extensions from './components/Extensions.jsx';
 import Gateway from './components/Gateway.jsx';
 import { I18nProvider } from '@lingui/react';
 import { i18n } from '@lingui/core';
@@ -113,7 +114,7 @@ class App extends React.Component {
 }
 
 function AppHTML() {
-  const theme = createMuiTheme(dashboardTheme);
+  const theme = createTheme(dashboardTheme);
 
   return (
     <React.Fragment>
@@ -211,6 +212,9 @@ function AppHTML() {
               <Route
                 path={`${pathPrefix}/community`}
                 render={props => <Navigation {...props} ChildComponent={Community} />} />
+              <Route
+                path={`${pathPrefix}/extensions`}
+                render={props => <Navigation {...props} ChildComponent={Extensions} />} />
               <Route component={NoMatch} />
             </Switch>
           </QueryParamProvider>
diff --git a/web/app/js/locales/en/messages.json b/web/app/js/locales/en/messages.json
index 42797ab0d..5c3fe9f43 100644
--- a/web/app/js/locales/en/messages.json
+++ b/web/app/js/locales/en/messages.json
@@ -19,6 +19,7 @@
   "buttonClose": "Close",
   "buttonCreateServiceProfile": "Create Service Profile",
   "buttonDownload": "Download",
+  "buttonLearnMore": "Learn More",
   "buttonReRunCheck": "Re-Run Check",
   "buttonReset": "Reset",
   "buttonRunLinkerdCheck": "Run Linkerd Check",
@@ -74,6 +75,7 @@
   "connectResourceMsg {resource}": "Connect your first {resource}",
   "controllerInstalledMsg": "Controller successfully installed",
   "createNewProfileMsg": "You can also create a new profile",
+  "extensionsPageMsg": "Linkerd provides a mix of built-in and third-party extensions to add additional functionality to the base installation. The following is the list of known extensions:",
   "formAuthority": "Authority",
   "formAuthorityHelpText": "Display requests with this :authority",
   "formCreateServiceProfileHelpText": "To create a service profile, download a profile and then apply it with `kubectl apply`.",
@@ -113,6 +115,7 @@
   "menuItemDaemonSets": "Daemon Sets",
   "menuItemDeployments": "Deployments",
   "menuItemDocumentation": "Documentation",
+  "menuItemExtension": "Extensions",
   "menuItemGateway": "Gateway",
   "menuItemGitHub": "GitHub",
   "menuItemJobs": "Jobs",
diff --git a/web/app/js/locales/es/messages.json b/web/app/js/locales/es/messages.json
index cbd6d03d7..62a01ea8f 100644
--- a/web/app/js/locales/es/messages.json
+++ b/web/app/js/locales/es/messages.json
@@ -19,6 +19,7 @@
   "buttonClose": "Cerrar",
   "buttonCreateServiceProfile": "Crear Service Profile",
   "buttonDownload": "Descargar",
+  "buttonLearnMore": "Aprende Más",
   "buttonReRunCheck": "Volver a Check",
   "buttonReset": "Restablecer",
   "buttonRunLinkerdCheck": "Ejecutar Linkerd Check",
@@ -113,6 +114,7 @@
   "menuItemDaemonSets": "Daemon Sets",
   "menuItemDeployments": "Deployments",
   "menuItemDocumentation": "Documentación",
+  "menuItemExtension": "Extensions",
   "menuItemGateway": "Gateway",
   "menuItemGitHub": "GitHub",
   "menuItemJobs": "Jobs",
diff --git a/web/app/package.json b/web/app/package.json
index 0eea37c29..42898894b 100644
--- a/web/app/package.json
+++ b/web/app/package.json
@@ -6,7 +6,7 @@
   "dependencies": {
     "@babel/eslint-plugin": "7.14.5",
     "@fortawesome/fontawesome-svg-core": "1.2.35",
-    "@fortawesome/free-regular-svg-icons": "5.15.3",
+    "@fortawesome/free-regular-svg-icons": "5.15.4",
     "@fortawesome/free-solid-svg-icons": "5.15.3",
     "@fortawesome/react-fontawesome": "0.1.15",
     "@lingui/core": "^3.10.4",
@@ -16,7 +16,7 @@
     "@material-ui/icons": "4.11.2",
     "@material-ui/lab": "^4.0.0-alpha.60",
     "classnames": "2.3.1",
-    "core-js": "^3.15.2",
+    "core-js": "^3.16.0",
     "css-mediaquery": "^0.1.2",
     "d3-drag": "3.0.0",
     "d3-force": "3.0.0",
@@ -44,7 +44,7 @@
     "@babel/core": "^7.14.8",
     "@babel/eslint-parser": "^7.14.9",
     "@babel/plugin-proposal-class-properties": "^7.13.0",
-    "@babel/preset-env": "^7.14.9",
+    "@babel/preset-env": "^7.15.0",
     "@babel/preset-react": "^7.14.5",
     "@babel/runtime": "^7.14.8",
     "@lingui/cli": "3.10.2",
diff --git a/web/app/yarn.lock b/web/app/yarn.lock
index b1b4ffb6c..837ed90c4 100644
--- a/web/app/yarn.lock
+++ b/web/app/yarn.lock
@@ -16,10 +16,10 @@
   dependencies:
     "@babel/highlight" "^7.14.5"
 
-"@babel/compat-data@^7.13.11", "@babel/compat-data@^7.14.5", "@babel/compat-data@^7.14.7", "@babel/compat-data@^7.14.9":
-  version "7.14.9"
-  resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.14.9.tgz#ac7996ceaafcf8f410119c8af0d1db4cf914a210"
-  integrity sha512-p3QjZmMGHDGdpcwEYYWu7i7oJShJvtgMjJeb0W95PPhSm++3lm8YXYOh45Y6iCN9PkZLTZ7CIX5nFrp7pw7TXw==
+"@babel/compat-data@^7.13.11", "@babel/compat-data@^7.14.7", "@babel/compat-data@^7.15.0":
+  version "7.15.0"
+  resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.15.0.tgz#2dbaf8b85334796cafbb0f5793a90a2fc010b176"
+  integrity sha512-0NqAC1IJE0S0+lL1SWFMxMkz1pKCNCjI4tr2Zx4LJSXxCLAdr6KyArnY+sno5m3yH9g737ygOyPABDsnXkpxiA==
 
 "@babel/core@^7.1.0", "@babel/core@^7.14.8", "@babel/core@^7.7.2", "@babel/core@^7.7.5":
   version "7.14.8"
@@ -67,6 +67,15 @@
     jsesc "^2.5.1"
     source-map "^0.5.0"
 
+"@babel/generator@^7.15.0":
+  version "7.15.0"
+  resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.15.0.tgz#a7d0c172e0d814974bad5aa77ace543b97917f15"
+  integrity sha512-eKl4XdMrbpYvuB505KTta4AV9g+wWzmVBW69tX0H2NwKVKd2YJbKgyK6M8j/rgLbmHOYJn6rUklV677nOyJrEQ==
+  dependencies:
+    "@babel/types" "^7.15.0"
+    jsesc "^2.5.1"
+    source-map "^0.5.0"
+
 "@babel/helper-annotate-as-pure@^7.14.5":
   version "7.14.5"
   resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.14.5.tgz#7bf478ec3b71726d56a8ca5775b046fc29879e61"
@@ -82,12 +91,12 @@
     "@babel/helper-explode-assignable-expression" "^7.14.5"
     "@babel/types" "^7.14.5"
 
-"@babel/helper-compilation-targets@^7.13.0", "@babel/helper-compilation-targets@^7.14.5":
-  version "7.14.5"
-  resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.14.5.tgz#7a99c5d0967911e972fe2c3411f7d5b498498ecf"
-  integrity sha512-v+QtZqXEiOnpO6EYvlImB6zCD2Lel06RzOPzmkz/D/XgQiUu3C/Jb1LOqSt/AIA34TYi/Q+KlT8vTQrgdxkbLw==
+"@babel/helper-compilation-targets@^7.13.0", "@babel/helper-compilation-targets@^7.14.5", "@babel/helper-compilation-targets@^7.15.0":
+  version "7.15.0"
+  resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.15.0.tgz#973df8cbd025515f3ff25db0c05efc704fa79818"
+  integrity sha512-h+/9t0ncd4jfZ8wsdAsoIxSa61qhBYlycXiHWqJaQBCXAhDCMbPRSMTGnZIkkmt1u4ag+UQmuqcILwqKzZ4N2A==
   dependencies:
-    "@babel/compat-data" "^7.14.5"
+    "@babel/compat-data" "^7.15.0"
     "@babel/helper-validator-option" "^7.14.5"
     browserslist "^4.16.6"
     semver "^6.3.0"
@@ -163,6 +172,13 @@
   dependencies:
     "@babel/types" "^7.14.5"
 
+"@babel/helper-member-expression-to-functions@^7.15.0":
+  version "7.15.0"
+  resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.15.0.tgz#0ddaf5299c8179f27f37327936553e9bba60990b"
+  integrity sha512-Jq8H8U2kYiafuj2xMTPQwkTBnEEdGKpT35lJEQsRRjnG0LW3neucsaMWLgKcwu3OHKNeYugfw+Z20BXBSEs2Lg==
+  dependencies:
+    "@babel/types" "^7.15.0"
+
 "@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.12.13", "@babel/helper-module-imports@^7.14.5":
   version "7.14.5"
   resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.14.5.tgz#6d1a44df6a38c957aa7c312da076429f11b422f3"
@@ -184,6 +200,20 @@
     "@babel/traverse" "^7.14.8"
     "@babel/types" "^7.14.8"
 
+"@babel/helper-module-transforms@^7.15.0":
+  version "7.15.0"
+  resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.15.0.tgz#679275581ea056373eddbe360e1419ef23783b08"
+  integrity sha512-RkGiW5Rer7fpXv9m1B3iHIFDZdItnO2/BLfWVW/9q7+KqQSDY5kUfQEbzdXM1MVhJGcugKV7kRrNVzNxmk7NBg==
+  dependencies:
+    "@babel/helper-module-imports" "^7.14.5"
+    "@babel/helper-replace-supers" "^7.15.0"
+    "@babel/helper-simple-access" "^7.14.8"
+    "@babel/helper-split-export-declaration" "^7.14.5"
+    "@babel/helper-validator-identifier" "^7.14.9"
+    "@babel/template" "^7.14.5"
+    "@babel/traverse" "^7.15.0"
+    "@babel/types" "^7.15.0"
+
 "@babel/helper-optimise-call-expression@^7.14.5":
   version "7.14.5"
   resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.14.5.tgz#f27395a8619e0665b3f0364cddb41c25d71b499c"
@@ -215,12 +245,15 @@
     "@babel/traverse" "^7.14.5"
     "@babel/types" "^7.14.5"
 
-"@babel/helper-simple-access@^7.14.5":
-  version "7.14.5"
-  resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.14.5.tgz#66ea85cf53ba0b4e588ba77fc813f53abcaa41c4"
-  integrity sha512-nfBN9xvmCt6nrMZjfhkl7i0oTV3yxR4/FztsbOASyTvVcoYd0TRHh7eMLdlEcCqobydC0LAF3LtC92Iwxo0wyw==
+"@babel/helper-replace-supers@^7.15.0":
+  version "7.15.0"
+  resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.15.0.tgz#ace07708f5bf746bf2e6ba99572cce79b5d4e7f4"
+  integrity sha512-6O+eWrhx+HEra/uJnifCwhwMd6Bp5+ZfZeJwbqUTuqkhIT6YcRhiZCOOFChRypOIe0cV46kFrRBlm+t5vHCEaA==
   dependencies:
-    "@babel/types" "^7.14.5"
+    "@babel/helper-member-expression-to-functions" "^7.15.0"
+    "@babel/helper-optimise-call-expression" "^7.14.5"
+    "@babel/traverse" "^7.15.0"
+    "@babel/types" "^7.15.0"
 
 "@babel/helper-simple-access@^7.14.8":
   version "7.14.8"
@@ -296,6 +329,11 @@
   resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.14.8.tgz#66fd41666b2d7b840bd5ace7f7416d5ac60208d4"
   integrity sha512-syoCQFOoo/fzkWDeM0dLEZi5xqurb5vuyzwIMNZRNun+N/9A4cUZeQaE7dTrB8jGaKuJRBtEOajtnmw0I5hvvA==
 
+"@babel/parser@^7.15.0":
+  version "7.15.0"
+  resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.15.0.tgz#b6d6e29058ca369127b0eeca2a1c4b5794f1b6b9"
+  integrity sha512-0v7oNOjr6YT9Z2RAOTv4T9aP+ubfx4Q/OhVtAet7PFDt0t9Oy6Jn+/rfC6b8HJ5zEqrQCiMxJfgtHpmIminmJQ==
+
 "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.14.5":
   version "7.14.5"
   resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.14.5.tgz#4b467302e1548ed3b1be43beae2cc9cf45e0bb7e"
@@ -677,14 +715,14 @@
     "@babel/helper-plugin-utils" "^7.14.5"
     babel-plugin-dynamic-import-node "^2.3.3"
 
-"@babel/plugin-transform-modules-commonjs@^7.14.5":
-  version "7.14.5"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.14.5.tgz#7aaee0ea98283de94da98b28f8c35701429dad97"
-  integrity sha512-en8GfBtgnydoao2PS+87mKyw62k02k7kJ9ltbKe0fXTHrQmG6QZZflYuGI1VVG7sVpx4E1n7KBpNlPb8m78J+A==
+"@babel/plugin-transform-modules-commonjs@^7.15.0":
+  version "7.15.0"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.15.0.tgz#3305896e5835f953b5cdb363acd9e8c2219a5281"
+  integrity sha512-3H/R9s8cXcOGE8kgMlmjYYC9nqr5ELiPkJn4q0mypBrjhYQoc+5/Maq69vV4xRPWnkzZuwJPf5rArxpB/35Cig==
   dependencies:
-    "@babel/helper-module-transforms" "^7.14.5"
+    "@babel/helper-module-transforms" "^7.15.0"
     "@babel/helper-plugin-utils" "^7.14.5"
-    "@babel/helper-simple-access" "^7.14.5"
+    "@babel/helper-simple-access" "^7.14.8"
     babel-plugin-dynamic-import-node "^2.3.3"
 
 "@babel/plugin-transform-modules-systemjs@^7.14.5":
@@ -840,13 +878,13 @@
     "@babel/helper-create-regexp-features-plugin" "^7.14.5"
     "@babel/helper-plugin-utils" "^7.14.5"
 
-"@babel/preset-env@^7.14.9":
-  version "7.14.9"
-  resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.14.9.tgz#4a3bbbd745f20e9121d5925170bef040a21b7819"
-  integrity sha512-BV5JvCwBDebkyh67bPKBYVCC6gGw0MCzU6HfKe5Pm3upFpPVqiC/hB33zkOe0tVdAzaMywah0LSXQeD9v/BYdQ==
+"@babel/preset-env@^7.15.0":
+  version "7.15.0"
+  resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.15.0.tgz#e2165bf16594c9c05e52517a194bf6187d6fe464"
+  integrity sha512-FhEpCNFCcWW3iZLg0L2NPE9UerdtsCR6ZcsGHUX6Om6kbCQeL5QZDqFDmeNHC6/fy6UH3jEge7K4qG5uC9In0Q==
   dependencies:
-    "@babel/compat-data" "^7.14.9"
-    "@babel/helper-compilation-targets" "^7.14.5"
+    "@babel/compat-data" "^7.15.0"
+    "@babel/helper-compilation-targets" "^7.15.0"
     "@babel/helper-plugin-utils" "^7.14.5"
     "@babel/helper-validator-option" "^7.14.5"
     "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.14.5"
@@ -894,7 +932,7 @@
     "@babel/plugin-transform-literals" "^7.14.5"
     "@babel/plugin-transform-member-expression-literals" "^7.14.5"
     "@babel/plugin-transform-modules-amd" "^7.14.5"
-    "@babel/plugin-transform-modules-commonjs" "^7.14.5"
+    "@babel/plugin-transform-modules-commonjs" "^7.15.0"
     "@babel/plugin-transform-modules-systemjs" "^7.14.5"
     "@babel/plugin-transform-modules-umd" "^7.14.5"
     "@babel/plugin-transform-named-capturing-groups-regex" "^7.14.9"
@@ -912,7 +950,7 @@
     "@babel/plugin-transform-unicode-escapes" "^7.14.5"
     "@babel/plugin-transform-unicode-regex" "^7.14.5"
     "@babel/preset-modules" "^0.1.4"
-    "@babel/types" "^7.14.9"
+    "@babel/types" "^7.15.0"
     babel-plugin-polyfill-corejs2 "^0.2.2"
     babel-plugin-polyfill-corejs3 "^0.2.2"
     babel-plugin-polyfill-regenerator "^0.2.2"
@@ -981,10 +1019,25 @@
     debug "^4.1.0"
     globals "^11.1.0"
 
-"@babel/types@^7.0.0", "@babel/types@^7.11.5", "@babel/types@^7.14.5", "@babel/types@^7.14.8", "@babel/types@^7.14.9", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.0", "@babel/types@^7.4.4":
-  version "7.14.9"
-  resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.14.9.tgz#f2b19c3f2f77c5708d67fe8f6046e9cea2b5036d"
-  integrity sha512-u0bLTnv3DFHeaQLYzb7oRJ1JHr1sv/SYDM7JSqHFFLwXG1wTZRughxFI5NCP8qBEo1rVVsn7Yg2Lvw49nne/Ow==
+"@babel/traverse@^7.15.0":
+  version "7.15.0"
+  resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.15.0.tgz#4cca838fd1b2a03283c1f38e141f639d60b3fc98"
+  integrity sha512-392d8BN0C9eVxVWd8H6x9WfipgVH5IaIoLp23334Sc1vbKKWINnvwRpb4us0xtPaCumlwbTtIYNA0Dv/32sVFw==
+  dependencies:
+    "@babel/code-frame" "^7.14.5"
+    "@babel/generator" "^7.15.0"
+    "@babel/helper-function-name" "^7.14.5"
+    "@babel/helper-hoist-variables" "^7.14.5"
+    "@babel/helper-split-export-declaration" "^7.14.5"
+    "@babel/parser" "^7.15.0"
+    "@babel/types" "^7.15.0"
+    debug "^4.1.0"
+    globals "^11.1.0"
+
+"@babel/types@^7.0.0", "@babel/types@^7.11.5", "@babel/types@^7.14.5", "@babel/types@^7.14.8", "@babel/types@^7.15.0", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.0", "@babel/types@^7.4.4":
+  version "7.15.0"
+  resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.15.0.tgz#61af11f2286c4e9c69ca8deb5f4375a73c72dcbd"
+  integrity sha512-OBvfqnllOIdX4ojTHpwZbpvz4j3EWyjkZEdmjH0/cgsd6QOdSgU8rLSk6ard/pcW7rlmjdVSX/AWOaORR1uNOQ==
   dependencies:
     "@babel/helper-validator-identifier" "^7.14.9"
     to-fast-properties "^2.0.0"
@@ -1037,10 +1090,10 @@
     minimatch "^3.0.4"
     strip-json-comments "^3.1.1"
 
-"@fortawesome/fontawesome-common-types@^0.2.35":
-  version "0.2.35"
-  resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.35.tgz#01dd3d054da07a00b764d78748df20daf2b317e9"
-  integrity sha512-IHUfxSEDS9dDGqYwIW7wTN6tn/O8E0n5PcAHz9cAaBoZw6UpG20IG/YM3NNLaGPwPqgjBAFjIURzqoQs3rrtuw==
+"@fortawesome/fontawesome-common-types@^0.2.35", "@fortawesome/fontawesome-common-types@^0.2.36":
+  version "0.2.36"
+  resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.36.tgz#b44e52db3b6b20523e0c57ef8c42d315532cb903"
+  integrity sha512-a/7BiSgobHAgBWeN7N0w+lAhInrGxksn13uK7231n2m8EDPE3BMCl9NZLTGrj9ZXfCmC6LM0QLqXidIizVQ6yg==
 
 "@fortawesome/fontawesome-svg-core@1.2.35":
   version "1.2.35"
@@ -1049,12 +1102,12 @@
   dependencies:
     "@fortawesome/fontawesome-common-types" "^0.2.35"
 
-"@fortawesome/free-regular-svg-icons@5.15.3":
-  version "5.15.3"
-  resolved "https://registry.yarnpkg.com/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-5.15.3.tgz#1ec4f2410ff638db549c5c5484fc60b66407dbe6"
-  integrity sha512-q4/p8Xehy9qiVTdDWHL4Z+o5PCLRChePGZRTXkl+/Z7erDVL8VcZUuqzJjs6gUz6czss4VIPBRdCz6wP37/zMQ==
+"@fortawesome/free-regular-svg-icons@5.15.4":
+  version "5.15.4"
+  resolved "https://registry.yarnpkg.com/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-5.15.4.tgz#b97edab436954333bbeac09cfc40c6a951081a02"
+  integrity sha512-9VNNnU3CXHy9XednJ3wzQp6SwNwT3XaM26oS4Rp391GsxVYA+0oDR2J194YCIWf7jNRCYKjUCOduxdceLrx+xw==
   dependencies:
-    "@fortawesome/fontawesome-common-types" "^0.2.35"
+    "@fortawesome/fontawesome-common-types" "^0.2.36"
 
 "@fortawesome/free-solid-svg-icons@5.15.3":
   version "5.15.3"
@@ -3137,10 +3190,10 @@ core-js-pure@^3.15.0:
   resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.15.1.tgz#8356f6576fa2aa8e54ca6fe02620968ff010eed7"
   integrity sha512-OZuWHDlYcIda8sJLY4Ec6nWq2hRjlyCqCZ+jCflyleMkVt3tPedDVErvHslyS2nbO+SlBFMSBJYvtLMwxnrzjA==
 
-core-js@^3.15.2:
-  version "3.15.2"
-  resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.15.2.tgz#740660d2ff55ef34ce664d7e2455119c5bdd3d61"
-  integrity sha512-tKs41J7NJVuaya8DxIOCnl8QuPHx5/ZVbFo1oKgVl1qHFBBrDctzQGtuLjPpRdNTWmKPH6oEvgN/MUID+l485Q==
+core-js@^3.16.0:
+  version "3.16.0"
+  resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.16.0.tgz#1d46fb33720bc1fa7f90d20431f36a5540858986"
+  integrity sha512-5+5VxRFmSf97nM8Jr2wzOwLqRo6zphH2aX+7KsAUONObyzakDNq2G/bgbhinxB4PoV9L3aXQYhiDKyIKWd2c8g==
 
 core-util-is@1.0.2, core-util-is@~1.0.0:
   version "1.0.2"
diff --git a/web/srv/server.go b/web/srv/server.go
index b2b288b9d..8219b3c62 100644
--- a/web/srv/server.go
+++ b/web/srv/server.go
@@ -176,6 +176,7 @@ func NewServer(
 	server.router.GET("/top", handler.handleIndex)
 	server.router.GET("/community", handler.handleIndex)
 	server.router.GET("/routes", handler.handleIndex)
+	server.router.GET("/extensions", handler.handleIndex)
 	server.router.GET("/profiles/new", handler.handleProfileDownload)
 
 	// add catch-all parameter to match all files in dir


Test patch:
diff --git a/test/integration/edges/edges_test.go b/test/integration/edges/edges_test.go
index c184de3d6..bde2de6de 100644
--- a/test/integration/edges/edges_test.go
+++ b/test/integration/edges/edges_test.go
@@ -26,12 +26,12 @@ func TestMain(m *testing.M) {
 // and linkerd-controller for edges to have been registered, which is the
 // case when running this test in the context of the other integration tests.
 
-// This test has been disabled because it can fail due to
-// https://github.com/linkerd/linkerd2/issues/3706
-// This test should be updated and re-enabled when that issue is addressed.
-/*
 func TestEdges(t *testing.T) {
 	ns := TestHelper.GetLinkerdNamespace()
+	promNs := TestHelper.GetVizNamespace()
+	if TestHelper.ExternalPrometheus() {
+		promNs = "external-prometheus"
+	}
 	cmd := []string{
 		"edges",
 		"-n", ns,
@@ -44,7 +44,10 @@ func TestEdges(t *testing.T) {
 	}
 
 	tpl := template.Must(template.ParseFiles("testdata/linkerd_edges.golden"))
-	vars := struct{ Ns string }{ns}
+	vars := struct {
+		Ns     string
+		PromNs string
+	}{ns, promNs}
 	var b bytes.Buffer
 	if err := tpl.Execute(&b, vars); err != nil {
 		t.Fatalf("failed to parse linkerd_edges.golden template: %s", err)
@@ -55,7 +58,6 @@ func TestEdges(t *testing.T) {
 		t.Errorf("Expected output:\n%s\nactual:\n%s", b.String(), out)
 	}
 }
-*/
 
 // TestDirectEdges deploys a terminus and then generates a load generator which
 // sends traffic directly to the pod ip of the terminus pod.
diff --git a/test/integration/edges/testdata/linkerd_edges.golden b/test/integration/edges/testdata/linkerd_edges.golden
index b8df0f117..5753225a5 100644
--- a/test/integration/edges/testdata/linkerd_edges.golden
+++ b/test/integration/edges/testdata/linkerd_edges.golden
@@ -1,20 +1,29 @@
 \[
   \{
-    "src": "linkerd\-controller",
-    "src_namespace": "{{.Ns}}",
-    "dst": "linkerd\-prometheus",
+    "src": "prometheus",
+    "src_namespace": "{{.PromNs}}",
+    "dst": "linkerd\-destination",
     "dst_namespace": "{{.Ns}}",
-    "client_id": "linkerd\-controller.{{.Ns}}",
-    "server_id": "linkerd\-prometheus.{{.Ns}}",
+    "client_id": "prometheus.{{.PromNs}}",
+    "server_id": "linkerd\-destination.{{.Ns}}",
     "no_tls_reason": ""
   \},
   \{
-    "src": "linkerd\-web",
-    "src_namespace": "{{.Ns}}",
-    "dst": "linkerd\-controller",
+    "src": "prometheus",
+    "src_namespace": "{{.PromNs}}",
+    "dst": "linkerd\-identity",
     "dst_namespace": "{{.Ns}}",
-    "client_id": "linkerd\-web.{{.Ns}}",
-    "server_id": "linkerd\-controller.{{.Ns}}",
+    "client_id": "prometheus.{{.PromNs}}",
+    "server_id": "linkerd\-identity.{{.Ns}}",
+    "no_tls_reason": ""
+  \},
+  \{
+    "src": "prometheus",
+    "src_namespace": "{{.PromNs}}",
+    "dst": "linkerd\-proxy\-injector",
+    "dst_namespace": "{{.Ns}}",
+    "client_id": "prometheus.{{.PromNs}}",
+    "server_id": "linkerd\-proxy\-injector.{{.Ns}}",
     "no_tls_reason": ""
   \}
 \]
\ No newline at end of file
diff --git a/viz/metrics-api/edges_test.go b/viz/metrics-api/edges_test.go
index c147d5458..119eca87f 100644
--- a/viz/metrics-api/edges_test.go
+++ b/viz/metrics-api/edges_test.go
@@ -2,6 +2,7 @@ package api
 
 import (
 	"context"
+	"fmt"
 	"testing"
 
 	"github.com/golang/protobuf/proto"
@@ -11,9 +12,9 @@ import (
 )
 
 const (
-	clientIDLabel = model.LabelName("client_id")
 	serverIDLabel = model.LabelName("server_id")
 	resourceLabel = model.LabelName("deployment")
+	podLabel      = model.LabelName("pod")
 )
 
 type edgesExpected struct {
@@ -22,18 +23,6 @@ type edgesExpected struct {
 	expectedResponse *pb.EdgesResponse // the edges response we expect
 }
 
-func genInboundPromSample(resourceNamespace, resourceName, clientID string) *model.Sample {
-	return &model.Sample{
-		Metric: model.Metric{
-			resourceLabel:  model.LabelValue(resourceName),
-			namespaceLabel: model.LabelValue(resourceNamespace),
-			clientIDLabel:  model.LabelValue(clientID),
-		},
-		Value:     123,
-		Timestamp: 456,
-	}
-}
-
 func genOutboundPromSample(resourceNamespace, resourceName, resourceNameDst, resourceNamespaceDst, serverID string) *model.Sample {
 	dstResourceLabel := "dst_" + resourceLabel
 
@@ -44,12 +33,33 @@ func genOutboundPromSample(resourceNamespace, resourceName, resourceNameDst, res
 			dstNamespaceLabel: model.LabelValue(resourceNamespaceDst),
 			dstResourceLabel:  model.LabelValue(resourceNameDst),
 			serverIDLabel:     model.LabelValue(serverID),
+			podLabel:          model.LabelValue(resourceName + "-0"),
 		},
 		Value:     123,
 		Timestamp: 456,
 	}
 }
 
+func genPod(name, namespace, sa string) string {
+	return fmt.Sprintf(`apiVersion: v1
+kind: Pod
+metadata:
+  name: %s
+  namespace: %s
+spec:
+  containers:
+  - name: linkerd-proxy
+    env:
+    - name: _l5d_ns
+      value: linkerd
+    - name: _l5d_trustdomain
+      value: cluster.local
+  serviceAccountName: %s
+status:
+  phase: Running
+`, name, namespace, sa)
+}
+
 func testEdges(t *testing.T, expectations []edgesExpected) {
 	for _, exp := range expectations {
 		mockProm, fakeGrpcServer, err := newMockGrpcServer(exp.expectedStatRPC)
@@ -94,15 +104,15 @@ func testEdges(t *testing.T, expectations []edgesExpected) {
 
 func TestEdges(t *testing.T) {
 	mockPromResponse := model.Vector{
-		genInboundPromSample("emojivoto", "emoji", "web.emojivoto.serviceaccount.identity.linkerd.cluster.local"),
-		genInboundPromSample("emojivoto", "voting", "web.emojivoto.serviceaccount.identity.linkerd.cluster.local"),
-		genInboundPromSample("emojivoto", "web", "default.emojivoto.serviceaccount.identity.linkerd.cluster.local"),
-		genInboundPromSample("linkerd", "linkerd-prometheus", "linkerd-identity.linkerd.identity.linkerd.cluster.local"),
-
 		genOutboundPromSample("emojivoto", "web", "emoji", "emojivoto", "emoji.emojivoto.serviceaccount.identity.linkerd.cluster.local"),
 		genOutboundPromSample("emojivoto", "web", "voting", "emojivoto", "voting.emojivoto.serviceaccount.identity.linkerd.cluster.local"),
 		genOutboundPromSample("emojivoto", "vote-bot", "web", "emojivoto", "web.emojivoto.serviceaccount.identity.linkerd.cluster.local"),
-		genOutboundPromSample("linkerd", "linkerd-identity", "linkerd-prometheus", "linkerd", "linkerd-prometheus.linkerd.identity.linkerd.cluster.local"),
+		genOutboundPromSample("linkerd", "linkerd-identity", "linkerd-prometheus", "linkerd", "linkerd-prometheus.linkerd.serviceaccount.identity.linkerd.cluster.local"),
+	}
+	pods := []string{
+		genPod("web-0", "emojivoto", "web"),
+		genPod("vote-bot-0", "emojivoto", "default"),
+		genPod("linkerd-identity-0", "linkerd", "linkerd-identity"),
 	}
 
 	t.Run("Successfully returns edges for resource type Deployment and namespace emojivoto", func(t *testing.T) {
@@ -111,6 +121,7 @@ func TestEdges(t *testing.T) {
 				expectedStatRPC: expectedStatRPC{
 					err:              nil,
 					mockPromResponse: mockPromResponse,
+					k8sConfigs:       pods,
 				},
 				req: &pb.EdgesRequest{
 					Selector: &pb.ResourceSelection{
@@ -132,6 +143,7 @@ func TestEdges(t *testing.T) {
 				expectedStatRPC: expectedStatRPC{
 					err:              nil,
 					mockPromResponse: mockPromResponse,
+					k8sConfigs:       pods,
 				},
 				req: &pb.EdgesRequest{
 					Selector: &pb.ResourceSelection{
@@ -153,6 +165,7 @@ func TestEdges(t *testing.T) {
 				expectedStatRPC: expectedStatRPC{
 					err:              nil,
 					mockPromResponse: mockPromResponse,
+					k8sConfigs:       pods,
 				},
 				req: &pb.EdgesRequest{
 					Selector: &pb.ResourceSelection{
diff --git a/viz/metrics-api/test_helper.go b/viz/metrics-api/test_helper.go
index 58a65c70b..533383b46 100644
--- a/viz/metrics-api/test_helper.go
+++ b/viz/metrics-api/test_helper.go
@@ -249,8 +249,8 @@ var linkerdEdgeRows = []*mockEdgeRow{
 		dst:          "linkerd-prometheus",
 		srcNamespace: "linkerd",
 		dstNamespace: "linkerd",
-		clientID:     "linkerd-identity.linkerd.identity.linkerd.cluster.local",
-		serverID:     "linkerd-prometheus.linkerd.identity.linkerd.cluster.local",
+		clientID:     "linkerd-identity.linkerd.serviceaccount.identity.linkerd.cluster.local",
+		serverID:     "linkerd-prometheus.linkerd.serviceaccount.identity.linkerd.cluster.local",
 		msg:          "",
 	},
 }