Merge pull request #10002 from hashicorp/ui/feature/banners-and-labels-for-tproxy-changes

This commit is contained in:
Freddy 2021-04-15 14:14:20 -06:00 committed by GitHub
commit daf897f1a9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 367 additions and 134 deletions

3
.changelog/10002.txt Normal file
View File

@ -0,0 +1,3 @@
```ui:enhancement
Transparent Proxy - Service mesh visualization updates
```

View File

@ -0,0 +1,3 @@
<span class="consul-transparent-proxy">
{{t "components.consul.transparent-proxy"}}
</span>

View File

@ -2,6 +2,9 @@
& {
min-width: 190px;
}
&.documentation {
min-width: 270px;
}
> div {
padding: 1rem;
}

View File

@ -1,4 +1,11 @@
<a class="card"
{{#if (eq @item.Name '* (All Services)')}}
<a class="topology-metrics-card" href={{href-to 'dc.services.index'}}>
<p class="empty">
{{@item.Name}}
</p>
</a>
{{else}}
<a class="topology-metrics-card"
href={{if
(and (env 'CONSUL_NSPACES_ENABLED') (not-eq @item.Namespace @service.Namespace))
(href-to "nspace.dc.services.show.index" (concat '~' @item.Namespace) @item.Datacenter @item.Name)
@ -10,6 +17,9 @@
<p>
{{@item.Name}}
</p>
{{#if (eq @item.Source 'proxy-registration')}}
<TopologyMetrics::SourceType />
{{/if}}
<div class="details">
{{#if (and (env 'CONSUL_NSPACES_ENABLED') (not-eq @item.Namespace @service.Namespace))}}
<dl class="nspace">
@ -54,3 +64,4 @@
</div>
{{yield}}
</a>
{{/if}}

View File

@ -0,0 +1,81 @@
#upstream-container .topology-metrics-card:not(:last-child),
#downstream-container .topology-metrics-card:not(:last-child) {
margin-bottom: 8px;
}
#upstream-container .topology-metrics-card,
#downstream-container .topology-metrics-card {
display: block;
color: $gray-700;
overflow: hidden;
background-color: $white;
border-radius: $decor-radius-100;
border: 1px solid $gray-200;
p {
padding: 12px 12px 0 12px;
font-size: $typo-size-500;
font-weight: $typo-weight-semibold;
margin-bottom: 0 !important;
}
p.empty {
padding: 12px !important;
}
div {
display: inline-flex;
dl {
display: inline-flex;
margin-right: 8px;
}
dd {
color: $gray-700;
}
span {
margin-right: 8px;
}
span::before,
dt::before {
margin-right: 4px;
}
.nspace dt::before,
.health dt::before {
margin-top: 2px;
}
.nspace dt::before {
@extend %with-folder-outline-mask, %as-pseudo;
}
.health dt::before {
@extend %with-help-circle-outline-mask, %as-pseudo;
}
.nspace dt::before {
@extend %with-folder-outline-mask, %as-pseudo;
}
.health dt::before {
@extend %with-help-circle-outline-mask, %as-pseudo;
}
.nspace dt::before,
.health dt::before {
background-color: $gray-500;
}
.passing::before {
@extend %with-check-circle-fill-color-mask, %as-pseudo;
background-color: $green-500;
}
.warning::before {
@extend %with-alert-triangle-color-mask, %as-pseudo;
background-color: $orange-500;
}
.critical::before {
@extend %with-cancel-square-fill-color-mask, %as-pseudo;
background-color: $red-500;
}
.empty::before {
@extend %with-minus-square-fill-mask, %as-pseudo;
color: $gray-500;
}
}
.details {
padding: 0 12px 12px 12px;
}
div.stats {
border-top: 1px solid $gray-200;
}
}

View File

@ -90,6 +90,13 @@
@item={{item}}
@oncreate={{action @oncreate item @service}}
/>
{{else if (and item.Intention.Allowed (not item.TransparentProxy) (eq item.Source 'specific-intention'))}}
<TopologyMetrics::Popover
@type='not-defined'
@position={{find-by 'id' (concat this.guid item.Namespace item.Name) this.iconPositions}}
@item={{item}}
@oncreate={{action @oncreate item @service}}
/>
{{/if}}
{{/each}}

View File

@ -81,15 +81,17 @@
@oncreate={{action @oncreate}}
/>
</div>
{{#if (gt @topology.Upstreams.length 0)}}
{{#if (gt this.upstreams.length 0)}}
<div id="upstream-column">
{{#each-in (group-by "Datacenter" @topology.Upstreams) as |dc upstreams|}}
{{#each-in (group-by "Datacenter" this.upstreams) as |dc upstreams|}}
<div
id="upstream-container"
{{did-insert this.setHeight 'upstream-lines'}}
{{did-update this.setHeight 'upstream-lines' @topology.Upstreams}}
{{did-update this.setHeight 'upstream-lines' this.upstreams}}
>
{{#if dc}}
<p>{{dc}}</p>
{{/if}}
{{#each upstreams as |item|}}
<TopologyMetrics::Card
@dc={{@dc}}
@ -119,7 +121,7 @@
@view={{this.upView}}
@center={{this.centerDimensions}}
@lines={{this.upLines}}
@items={{@topology.Upstreams}}
@items={{this.upstreams}}
@oncreate={{action @oncreate}}
/>
</div>

View File

@ -1,6 +1,6 @@
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
import { action, get } from '@ember/object';
export default class TopologyMetrics extends Component {
// =attributes
@ -66,6 +66,24 @@ export default class TopologyMetrics extends Component {
});
}
get upstreams() {
const upstreams = get(this.args.topology, 'Upstreams') || [];
const items = [...upstreams];
const defaultAllow = get(this.args.topology, 'DefaultAllow');
const wildcardIntention = get(this.args.topology, 'WildcardIntention');
if (defaultAllow || wildcardIntention) {
items.push({
Name: '* (All Services)',
Datacenter: '',
Namespace: '',
Intention: {
Allowed: true,
},
});
}
return items;
}
// =actions
@action
setHeight(el, item) {
@ -89,12 +107,22 @@ export default class TopologyMetrics extends Component {
// Calculate viewBox dimensions
this.downView = document.getElementById('downstream-lines').getBoundingClientRect();
this.upView = document.getElementById('upstream-lines').getBoundingClientRect();
const upstreamLines = document.getElementById('upstream-lines').getBoundingClientRect();
const upstreamColumn = document.getElementById('upstream-column').getBoundingClientRect();
this.upView = {
x: upstreamLines.x,
y: upstreamLines.y,
width: upstreamLines.width,
height: upstreamColumn.height,
};
// Get Card elements positions
const downCards = [...document.querySelectorAll('#downstream-container .card')];
const downCards = [
...document.querySelectorAll('#downstream-container .topology-metrics-card'),
];
const grafanaCard = document.querySelector('.metrics-header');
const upCards = [...document.querySelectorAll('#upstream-column .card')];
const upCards = [...document.querySelectorAll('#upstream-column .topology-metrics-card')];
// Set center positioning points
this.centerDimensions = grafanaCard.getBoundingClientRect();

View File

@ -49,44 +49,6 @@
#upstream-column #upstream-container:not(:last-child) {
margin-bottom: 8px;
}
#upstream-container .card:not(:last-child),
#downstream-container .card:not(:last-child) {
margin-bottom: 8px;
}
#upstream-container .card,
#downstream-container .card {
display: block;
color: $gray-700;
overflow: hidden;
p {
padding: 12px 12px 0 12px;
font-size: 16px;
font-weight: 600;
margin-bottom: 0 !important;
}
div {
display: inline-flex;
dl {
display: inline-flex;
margin-right: 8px;
}
span {
margin-right: 8px;
}
span::before,
dt::before {
margin-right: 4px;
}
.nspace dt::before,
.health dt::before {
margin-top: 2px;
}
}
.details {
padding: 0 12px 12px 12px;
}
}
// Metrics Container
#metrics-container {

View File

@ -0,0 +1,31 @@
<Notice
class="topology-metrics-notice"
...attributes
@type={{@type}}
as |notice|>
<notice.Header>
<h3>
{{t (concat "components.consul.topology-metrics.notice." @for ".header")}}
</h3>
</notice.Header>
<notice.Body>
<p>
{{t (concat "components.consul.topology-metrics.notice." @for ".body")}}
</p>
</notice.Body>
{{#if @action}}
<notice.Footer>
<p>
{{#if @internal}}
<Action @href={{href-to (t (concat "components.consul.topology-metrics.notice." @for ".footer.URL"))}}>
{{t (concat "components.consul.topology-metrics.notice." @for ".footer.name")}}
</Action>
{{else}}
<Action @href={{@link}} @external={{true}}>
{{t (concat "components.consul.topology-metrics.notice." @for ".footer")}}
</Action>
{{/if}}
</p>
</notice.Footer>
{{/if}}
</Notice>

View File

@ -1,16 +0,0 @@
<Notice
class="topology-metrics-notice-limited-access"
...attributes
@type={{or @type "info"}}
as |notice|>
<notice.Header>
<h3>
Limited Access
</h3>
</notice.Header>
<notice.Body>
<p>
This service may have dependencies you wont see because you dont have access to them.
</p>
</notice.Body>
</Notice>

View File

@ -4,22 +4,21 @@
>
{{#if (eq @type 'deny')}}
<InformedAction
class="dangerous"
{{did-insert (set this 'popover')}}
>
<:header>
<h3>
Connection Denied
{{t "components.consul.topology-metrics.popover.deny.header"}}
</h3>
</:header>
<:body>
<p>
{{#if @item.Intention.HasExact}}
Change the action of this intention to allow.
{{t "components.consul.topology-metrics.popover.deny.body.isExact"}}
{{else}}
Add an intention that allows these two services to connect.
{{t "components.consul.topology-metrics.popover.deny.body.notExact"}}
{{/if}}
</p>
</:body>
@ -31,9 +30,9 @@
type="button"
>
{{#if @item.Intention.HasExact}}
Allow
{{t "components.consul.topology-metrics.popover.deny.action.isExact"}}
{{else}}
Create
{{t "components.consul.topology-metrics.popover.deny.action.notExact"}}
{{/if}}
</button>
</Actions.Action>
@ -48,29 +47,57 @@
</Actions.Action>
</:actions>
</InformedAction>
{{else}}
{{else if (eq @type 'not-defined')}}
<InformedAction
class="info"
class="warning documentation"
{{did-insert (set this 'popover')}}
>
<:header>
<h3>
Layer 7 permissions
{{t "components.consul.topology-metrics.popover.not-defined.header"}}
</h3>
</:header>
<:body>
<p>
Certain HTTP request info must be identified.
{{t "components.consul.topology-metrics.popover.not-defined.body"}}
</p>
</:body>
<:actions as |Actions|>
<Actions.Action class="action">
<a
href={{href-to 'dc.services.show.intentions.edit' (concat @item.Intention.ID)}}
>
View
<a href="{{env 'CONSUL_DOCS_URL'}}/connect/registration/service-registration#upstreams" rel="noopener noreferrer" target="_blank">
{{t "components.consul.topology-metrics.popover.not-defined.action"}}
</a>
</Actions.Action>
<Actions.Action>
<button
{{on 'click' (fn (optional this.popoverController.hide))}}
class="cancel"
type="button"
>
Close
</button>
</Actions.Action>
</:actions>
</InformedAction>
{{else}}
<InformedAction
class="info"
{{did-insert (set this 'popover')}}
>
<:header>
<h3>
{{t "components.consul.topology-metrics.popover.l7.header"}}
</h3>
</:header>
<:body>
<p>
{{t "components.consul.topology-metrics.popover.l7.body"}}
</p>
</:body>
<:actions as |Actions|>
<Actions.Action class="action">
<a href={{href-to 'dc.services.show.intentions.edit' (concat @item.Intention.ID)}}>
{{t "components.consul.topology-metrics.popover.l7.action"}}
</a>
</Actions.Action>
<Actions.Action>
@ -84,7 +111,6 @@
</Actions.Action>
</:actions>
</InformedAction>
{{/if}}
<button
{{with-overlay

View File

@ -27,4 +27,9 @@
@extend %with-layers-mask, %as-pseudo;
background-color: $gray-300;
}
&.not-defined > button::before,
&.not-defined .tippy-arrow::after {
@extend %with-alert-triangle-mask, %as-pseudo;
color: $yellow-500;
}
}

View File

@ -20,52 +20,6 @@
background-color: $gray-500;
}
}
#upstream-container .card,
#downstream-container .card {
background-color: $white;
border-radius: $decor-radius-100;
border: 1px solid $gray-200;
div {
dd {
color: $gray-700;
}
.nspace dt::before {
@extend %with-folder-outline-mask, %as-pseudo;
}
.health dt::before {
@extend %with-help-circle-outline-mask, %as-pseudo;
}
.nspace dt::before {
@extend %with-folder-outline-mask, %as-pseudo;
}
.health dt::before {
@extend %with-help-circle-outline-mask, %as-pseudo;
}
.nspace dt::before,
.health dt::before {
background-color: $gray-500;
}
.passing::before {
@extend %with-check-circle-fill-color-mask, %as-pseudo;
background-color: $green-500;
}
.warning::before {
@extend %with-alert-triangle-color-mask, %as-pseudo;
background-color: $orange-500;
}
.critical::before {
@extend %with-cancel-square-fill-color-mask, %as-pseudo;
background-color: $red-500;
}
.empty::before {
@extend %with-minus-square-fill-mask, %as-pseudo;
color: $gray-500;
}
}
div:nth-child(3) {
border-top: 1px solid $gray-200;
}
}
// Metrics Container
#metrics-container {
@ -90,9 +44,6 @@
@extend %with-docs-mask, %as-pseudo;
}
}
div:nth-child(3) {
border-top: 1px solid $gray-200;
}
}
// SVG Line styling
@ -114,6 +65,9 @@
stroke: $gray-300;
stroke-width: 2;
}
path[data-permission='not-defined'] {
stroke-dasharray: 4;
}
path[data-permission='deny'] {
stroke: $red-500;
}

View File

@ -0,0 +1,6 @@
<span
class="topology-metrics-source-type"
{{tooltip (t "components.consul.topology-metrics.source-type.tooltip")}}
>
{{t "components.consul.topology-metrics.source-type.text"}}
</span>

View File

@ -0,0 +1,4 @@
.topology-metrics-source-type {
margin: 6px 0 6px 12px;
display: table;
}

View File

@ -3,12 +3,15 @@ import { helper } from '@ember/component/helper';
export default helper(function serviceIntentionPermissions([params] /*, hash*/) {
const hasPermissions = params.Intention.HasPermissions;
const allowed = params.Intention.Allowed;
const notExplicitlyDefined = params.Source === 'specific-intention' && !params.TransparentProxy;
switch (true) {
case hasPermissions:
return 'allow';
case !allowed && !hasPermissions:
return 'deny';
case allowed && notExplicitlyDefined:
return 'not-defined';
default:
return 'allow';
}

View File

@ -1,4 +1,5 @@
import Model, { attr } from '@ember-data/model';
import { computed } from '@ember/object';
export const PRIMARY_KEY = 'uid';
export const SLUG_KEY = 'ServiceName';
@ -11,7 +12,30 @@ export default class Topology extends Model {
@attr('string') Namespace;
@attr('string') Protocol;
@attr('boolean') FilteredByACLs;
@attr('boolean') TransparentProxy;
@attr('boolean') DefaultAllow;
@attr('boolean') WildcardIntention;
@attr() Upstreams; // Service[]
@attr() Downstreams; // Service[],
@attr() meta; // {}
@computed('Upstreams', 'Downstreams')
get undefinedIntention() {
let undefinedUpstream = false;
let undefinedDownstream = false;
undefinedUpstream =
this.Upstreams.filter(
item =>
item.Source === 'specific-intention' && !item.TransparentProxy && item.Intention.Allowed
).length !== 0;
undefinedDownstream =
this.Downstreams.filter(
item =>
item.Source === 'specific-intention' && !item.TransparentProxy && item.Intention.Allowed
).length !== 0;
return undefinedUpstream || undefinedDownstream;
}
}

View File

@ -79,6 +79,8 @@
@import 'consul-ui/components/role-selector';
@import 'consul-ui/components/topology-metrics';
@import 'consul-ui/components/topology-metrics/card';
@import 'consul-ui/components/topology-metrics/source-type';
@import 'consul-ui/components/topology-metrics/popover';
@import 'consul-ui/components/topology-metrics/series';
@import 'consul-ui/components/topology-metrics/stats';

View File

@ -1,7 +1,9 @@
span.policy-service-identity,
span.policy-node-identity,
.leader,
.consul-auth-method-type {
.consul-auth-method-type,
.topology-metrics-source-type,
.consul-transparent-proxy {
@extend %pill-200, %frame-gray-600;
}
span.policy-service-identity::before,

View File

@ -58,6 +58,9 @@ as |route|>
</h1>
<Consul::ExternalSource @item={{item}} />
<Consul::Kind @item={{item}} @withInfo={{true}} />
{{#if (eq proxy.ServiceProxy.Mode 'transparent')}}
<Consul::TransparentProxy />
{{/if}}
</BlockSlot>
<BlockSlot @name="nav">
<dl>

View File

@ -23,7 +23,36 @@ as |route|>
</EmptyState>
{{else}}
{{#if topology.FilteredByACLs}}
<TopologyMetrics::Notice::LimitedAccess />
<TopologyMetrics::Notice
@type="info"
@for="limited-access"
@action={{false}}
/>
{{/if}}
{{#if topology.DefaultAllow}}
<TopologyMetrics::Notice
@type="warning"
@for="default-allow"
@internal={{true}}
@action={{true}}
/>
{{/if}}
{{#if topology.WildcardIntention}}
<TopologyMetrics::Notice
@type="warning"
@for="wildcard-intention"
@internal={{true}}
@action={{true}}
/>
{{/if}}
{{#if topology.undefinedIntention}}
<TopologyMetrics::Notice
@type="warning"
@for="not-defined-intention"
@link="{{env 'CONSUL_DOCS_URL'}}/connect/registration/service-registration#upstreams"
@internal={{false}}
@action={{true}}
/>
{{/if}}
<TopologyMetrics
@nspace={{nspace}}

View File

@ -49,6 +49,7 @@ ${range(env('CONSUL_EXPOSED_COUNT', 3)).map((i) => `
`)}
]
},
"Mode": "${fake.helpers.randomize(['', 'direct', 'transparent'])}",
"DestinationServiceName": "${location.pathname.slice(4)}"
${ location.pathname.slice(4) === "service-0" ? `
,

View File

@ -52,10 +52,18 @@ ${
}
fake.seed(index);
// Randomly make permissive intentions
const defaultAllow = fake.random.boolean();
const wildcardIntention = defaultAllow ? false : fake.random.boolean();
return `
{
"Protocol": "${serviceProto}",
"FilteredByACLs": ${fake.random.boolean()},
"TransparentProxy": ${fake.random.boolean()},
"DefaultAllow": ${defaultAllow},
"WildcardIntention": ${wildcardIntention},
"Upstreams": [
${
upstreams.map((item, i) => {
@ -70,11 +78,14 @@ ${
"ChecksPassing":${fake.random.number({min: 1, max: env('CONSUL_CHECK_COUNT', fake.random.number(10))})},
"ChecksWarning":${fake.random.number({min: 0, max: env('CONSUL_CHECK_COUNT', fake.random.number(10))})},
"ChecksCritical":${fake.random.number({min: 0, max: env('CONSUL_CHECK_COUNT', fake.random.number(10))})},
"Source": "${fake.helpers.randomize(['proxy-registration', 'default-allow', 'wildcard-intention'])}",
"TransparentProxy": ${fake.random.boolean()},
"Intention": {
"Allowed": ${allowed},
"HasPermissions": ${hasPerms},
"ExternalSource": "${fake.helpers.randomize(['nomad', 'kubernetes', ''])}",
"HasExact": ${fake.random.boolean()}
"HasExact": ${fake.random.boolean()},
"DefaultAllow": ${fake.random.boolean()}
}
}
`})}
@ -93,11 +104,14 @@ ${
"ChecksPassing":${fake.random.number({min: 1, max: env('CONSUL_CHECK_COUNT', fake.random.number(10))})},
"ChecksWarning":${fake.random.number({min: 0, max: env('CONSUL_CHECK_COUNT', fake.random.number(10))})},
"ChecksCritical":${fake.random.number({min: 0, max: env('CONSUL_CHECK_COUNT', fake.random.number(10))})},
"Source": "${fake.helpers.randomize(['proxy-registration', 'specific-intention', 'default-allow', 'wildcard-intention'])}",
"TransparentProxy": ${fake.random.boolean()},
"Intention": {
"Allowed": ${allowed},
"HasPermissions": ${hasPerms},
"ExternalSource": "${fake.helpers.randomize(['nomad', 'kubernetes', ''])}",
"HasExact": ${fake.random.boolean()}
"HasExact": ${fake.random.boolean()},
"DefaultAllow": ${fake.random.boolean()}
}
}
`})}

View File

@ -184,6 +184,51 @@ components:
name: Precedence
asc: Ascending
desc: Descending
transparent-proxy: Transparent Proxy
topology-metrics:
source-type:
tooltip: This connection was defined in a proxy registration.
text: Defined in proxy registration
notice:
limited-access:
header: Limited Access
body: This service may have dependencies you wont see because you dont have access to them.
default-allow:
header: Intentions are set to default allow
body: Your Intention settings are currently set to default allow. This means that this view will show connections to every service in your cluster. We recommend changing your Intention settings to default deny and creating specific Intentions for upstream and downstream services for this view to be useful.
footer:
name: Edit intentions
URL: dc.services.show.intentions
not-defined-intention:
header: Connections are not explicitly defined
body: There appears to be an Intention defining traffic, but the services are unable to communicate until that connection is explicitly defined as a downstream or Transparent Proxy mode is turned on.
footer: Read the documentation
wildcard-intention:
header: Permissive Intention
body: One or more of your Intentions are set to allow traffic to and/or from all other services in a namespace. This Topology view will show all of those connections if that remains unchanged. We recommend setting more specific Intentions for upstream and downstream services to make this vizualization more useful.
footer:
name: Edit intentions
URL: dc.services.show.intentions
popover:
l7:
header: Layer 7 permissions
body: Certain HTTP request info must be identified.
action: View
deny:
header: Connection Denied
body:
isExact: Change the action of this intention to allow.
notExact: Add an intention that allows these two services to connect.
action:
isExact: Allow
notExact: Create
not-defined:
header: No traffic
body: Add the current service as an explicit upstream or turn on Transparent Proxy mode to initiate traffic.
action: Documentation
models:
auth-method: