ui: Topology view with no dependencies (#11280)

This commit is contained in:
Kenia 2021-11-05 13:46:41 -04:00 committed by GitHub
parent b3af482e09
commit 24d8236219
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 152 additions and 58 deletions

3
.changelog/11280.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:feature
ui: Topology - New views for scenarios where no dependencies exist or ACLs are disabled
```

View File

@ -1,5 +1,9 @@
{{#if (eq @item.Name '* (All Services)')}}
<a data-test-topology-metrics-card class="topology-metrics-card" href={{href-to 'dc.services.index'}}>
{{#if (eq @item.Datacenter '')}}
<a
class="topology-metrics-card"
href={{href-to 'dc.services.index'}}
data-permission={{service/card-permissions @item}}
>
<p class="empty">
{{@item.Name}}
</p>
@ -12,7 +16,7 @@
(href-to this.hrefPath @item.Datacenter @item.Name params=(hash nspace=@item.Namespace))
(href-to this.hrefPath @item.Name)
}}
data-permission={{service/intention-permissions @item}}
data-permission={{service/card-permissions @item}}
id="{{@item.Namespace}}{{@item.Name}}"
>
<p>

View File

@ -84,7 +84,7 @@
{{#let (not (can 'update intention for service' item=@service.Service)) as |disabled|}}
{{#each @items as |item|}}
{{#if (or (not item.Intention.Allowed) item.Intention.HasPermissions)}}
{{#if (and (not-eq item.Datacenter '') (or (not item.Intention.Allowed) item.Intention.HasPermissions))}}
<TopologyMetrics::Popover
@type={{if item.Intention.HasPermissions 'l7' 'deny'}}
@position={{find-by 'id' (concat this.guid item.Namespace item.Name) this.iconPositions}}
@ -92,7 +92,7 @@
@disabled={{disabled}}
@oncreate={{action @oncreate item @service}}
/>
{{else if (and item.Intention.Allowed (not item.TransparentProxy) (eq item.Source 'specific-intention'))}}
{{else if (and (not-eq item.Datacenter '') item.Intention.Allowed (not item.TransparentProxy) (eq item.Source 'specific-intention'))}}
<TopologyMetrics::Popover
@type='not-defined'
@service={{@service}}

View File

@ -2,12 +2,13 @@
{{on-resize this.calculate}}
class="topology-container consul-topology-metrics"
>
{{#if (gt @topology.Downstreams.length 0)}}
{{#if (gt this.downstreams.length 0)}}
<div
id="downstream-container"
{{did-insert this.setHeight 'downstream-lines'}}
{{did-update this.setHeight 'downstream-lines' @topology.Downstreams}}
{{did-update this.setHeight 'downstream-lines' this.downstreams}}
>
{{#if (not this.emptyColumn)}}
<div>
<p>{{@dc.Name}}</p>
<span>
@ -16,7 +17,8 @@
</Tooltip>
</span>
</div>
{{#each @topology.Downstreams as |item|}}
{{/if}}
{{#each this.downstreams as |item|}}
<TopologyMetrics::Card
@nspace={{@nspace}}
@dc={{@dc.Name}}
@ -82,7 +84,7 @@
@view={{this.downView}}
@center={{this.centerDimensions}}
@lines={{this.downLines}}
@items={{@topology.Downstreams}}
@items={{this.downstreams}}
@oncreate={{action @oncreate}}
/>
</div>

View File

@ -1,8 +1,11 @@
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { action, get } from '@ember/object';
import { inject as service } from '@ember/service';
export default class TopologyMetrics extends Component {
@service('env') env;
// =attributes
@tracked centerDimensions;
@tracked downView;
@ -66,19 +69,58 @@ export default class TopologyMetrics extends Component {
});
}
emptyColumn() {
const noDependencies = get(this.args.topology, 'noDependencies');
return !this.env.var('CONSUL_ACLS_ENABLED') || noDependencies;
}
get downstreams() {
const downstreams = get(this.args.topology, 'Downstreams') || [];
const items = [...downstreams];
const noDependencies = get(this.args.topology, 'noDependencies');
if (!this.env.var('CONSUL_ACLS_ENABLED') && noDependencies) {
items.push({
Name: 'Downstreams unknown.',
Empty: true,
Datacenter: '',
Namespace: '',
});
} else if (downstreams.length === 0) {
items.push({
Name: 'No downstreams.',
Datacenter: '',
Namespace: '',
});
}
return items;
}
get upstreams() {
const upstreams = get(this.args.topology, 'Upstreams') || [];
const items = [...upstreams];
const defaultACLPolicy = get(this.args.dc, 'DefaultACLPolicy');
const wildcardIntention = get(this.args.topology, 'wildcardIntention');
if (defaultACLPolicy === 'allow' || wildcardIntention) {
const noDependencies = get(this.args.topology, 'noDependencies');
if (!this.env.var('CONSUL_ACLS_ENABLED') && noDependencies) {
items.push({
Name: 'Upstreams unknown.',
Datacenter: '',
Namespace: '',
});
} else if (defaultACLPolicy === 'allow' || wildcardIntention) {
items.push({
Name: '* (All Services)',
Datacenter: '',
Namespace: '',
Intention: {
Allowed: true,
},
});
} else if (upstreams.length === 0) {
items.push({
Name: 'No upstreams.',
Datacenter: '',
Namespace: '',
});
}
return items;
@ -112,10 +154,21 @@ export default class TopologyMetrics extends Component {
}
// Calculate viewBox dimensions
this.downView = document.getElementById('downstream-lines').getBoundingClientRect();
const downstreamLines = document.getElementById('downstream-lines').getBoundingClientRect();
const upstreamLines = document.getElementById('upstream-lines').getBoundingClientRect();
const upstreamColumn = document.getElementById('upstream-column');
if (this.emptyColumn) {
this.downView = {
x: downstreamLines.x,
y: downstreamLines.y,
width: downstreamLines.width,
height: downstreamLines.height + 10,
};
} else {
this.downView = downstreamLines;
}
if (upstreamColumn) {
this.upView = {
x: upstreamLines.x,

View File

@ -65,7 +65,8 @@
stroke: rgb(var(--tone-gray-300));
stroke-width: 2;
}
path[data-permission='not-defined'] {
path[data-permission='not-defined'],
path[data-permission='empty'] {
stroke-dasharray: 4;
}
path[data-permission='deny'] {

View File

@ -83,7 +83,7 @@
</svg>
{{/if}}
{{#each @items as |item|}}
{{#if (or (not item.Intention.Allowed) item.Intention.HasPermissions)}}
{{#if (and (not-eq item.Datacenter '') (or (not item.Intention.Allowed) item.Intention.HasPermissions))}}
<TopologyMetrics::Popover
@type={{if item.Intention.HasPermissions 'l7' 'deny'}}
@position={{find-by 'id' (concat this.guid item.Namespace item.Name) this.iconPositions}}

View File

@ -0,0 +1,22 @@
import { helper } from '@ember/component/helper';
export default helper(function serviceCardPermissions([params] /*, hash*/) {
if (params.Datacenter === '') {
return 'empty';
} else {
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,18 +0,0 @@
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

@ -46,4 +46,9 @@ export default class Topology extends Model {
return downstreamWildcard || upstreamWildcard;
}
@computed('Downstreams', 'Upstreams')
get noDependencies() {
return this.Upstreams.length === 0 && this.Downstreams.length === 0;
}
}

View File

@ -27,26 +27,6 @@ as |route|>
loader.data
as |nspace dc items topology|}}
<div class="tab-section">
{{#if (and (eq topology.Upstreams.length 0) (eq topology.Downstreams.length 0) (not-eq dc.DefaultACLPolicy 'allow') (not topology.wildcardIntention))}}
<EmptyState>
<BlockSlot @name="header">
<h2>
No dependencies
</h2>
</BlockSlot>
<BlockSlot @name="body">
<p>
This service has neither downstreams nor upstreams, which means that no services are configured to connect with it. Add upstreams and intentions to ensure this service is connected with the rest of your service mesh.
</p>
</BlockSlot>
<BlockSlot @name="actions">
<li class="docs-link">
<a href="{{env 'CONSUL_DOCS_URL'}}/connect/registration/service-registration#complete-configuration-example" rel="noopener noreferrer" target="_blank">Documentation on upstreams</a>
</li>
</BlockSlot>
</EmptyState>
{{else}}
{{#let (collapsible-notices topology.FilteredByACLs (eq dc.DefaultACLPolicy 'allow') topology.wildcardIntention topology.notDefinedIntention) as |collapsible| }}
<CollapsibleNotices @collapsible={{collapsible}}>
{{#if topology.FilteredByACLs}}
@ -85,6 +65,26 @@ as |nspace dc items topology|}}
@action={{true}}
/>
{{/if}}
{{#if (and topology.noDependencies (can 'use acls'))}}
<TopologyMetrics::Notice
data-test-notice='no-dependencies'
@type="info"
@for="no-dependencies"
@link="{{env 'CONSUL_DOCS_URL'}}/connect/registration/service-registration#upstream-configuration-reference"
@internal={{false}}
@action={{true}}
/>
{{/if}}
{{#if (and topology.noDependencies (not (can 'use acls')))}}
<TopologyMetrics::Notice
data-test-notice='acls-disabled'
@type="warning"
@for="acls-disabled"
@link="{{env 'CONSUL_DOCS_URL'}}/security/acl/acl-system#configuring-acls"
@internal={{false}}
@action={{true}}
/>
{{/if}}
</CollapsibleNotices>
{{/let}}
<DataSource
@ -113,8 +113,6 @@ as |nspace dc items topology|}}
/>
{{/if}}
</DataSource>
{{/if}}
</div>
{{/let}}
</BlockSlot>

View File

@ -0,0 +1,10 @@
import steps from '../../../../steps';
// step definitions that are shared between features should be moved to the
// tests/acceptance/steps/steps.js file
export default function(assert) {
return steps(assert).then('I should find a file', function() {
assert.ok(true, this.step);
});
}

View File

@ -3,7 +3,7 @@ import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
module('Integration | Helper | service/intention-permissions', function(hooks) {
module('Integration | Helper | service/card-permissions', function(hooks) {
setupRenderingTest(hooks);
// TODO: Replace this with your real tests.
@ -15,7 +15,7 @@ module('Integration | Helper | service/intention-permissions', function(hooks) {
},
});
await render(hbs`{{service/intention-permissions inputValue}}`);
await render(hbs`{{service/card-permissions inputValue}}`);
assert.equal(this.element.textContent.trim(), 'allow');
});

View File

@ -48,6 +48,12 @@ export default function(
notDefinedIntention: {
see: isPresent('[data-test-notice="not-defined-intention"]'),
},
noDependencies: {
see: isPresent('[data-test-notice="no-dependencies"]'),
},
aclsDisabled: {
see: isPresent('[data-test-notice="acls-disabled"]'),
},
};
page.tabs.upstreamsTab = {
services: collection('.consul-upstream-list > ul > li:not(:first-child)', {

View File

@ -143,6 +143,14 @@ topology-metrics:
footer:
name: Edit intentions
URL: dc.services.show.intentions
no-dependencies:
header: No dependencies
body: The service you are viewing currently has no dependencies. You will only see metrics for the current service until dependencies are added.
footer: Read the documentation
acls-disabled:
header: Enable ACLs
body: This connect-native service may have dependencies, but Consul isn't aware of them when ACLs are disabled. Enable ACLs to make this view more useful.
footer: Read the documentation
popover:
l7:
header: Layer 7 permissions