mirror of https://github.com/status-im/consul.git
ui: Add deny SVG lines with icons (#8846)
* Refactor and color SVG Lines based on intention permissions * Create Icon component with L7 and Deny icon styling * Reposition icons on the lines when the lines are redrawn * Create service/intention-permissions helper * Use service/intention-permissions helper to return allow or deny lines * Upgrade consul-api-double to v5.3.5 * Update HasPermission attribute
This commit is contained in:
parent
164ce57db2
commit
b373456c76
|
@ -0,0 +1,62 @@
|
|||
{{on-window 'resize' (action this.getIconPositions)}}
|
||||
|
||||
{{#if (gt @lines.length 0)}}
|
||||
<svg
|
||||
{{did-insert this.getIconPositions}}
|
||||
{{did-update this.getIconPositions @lines}}
|
||||
viewBox={{concat @view.x ' ' @view.y ' ' @view.width ' ' @view.height}}
|
||||
preserveAspectRatio="none"
|
||||
>
|
||||
<defs>
|
||||
<marker id="allow-dot" viewBox="-2 -2 15 15" refX="6" refY="6" markerWidth="6" markerHeight="6">
|
||||
<circle
|
||||
cx="6"
|
||||
cy="6"
|
||||
r="6"
|
||||
/>
|
||||
</marker>
|
||||
<marker id="allow-arrow" viewBox="-1 -1 12 12" refX="5" refY="5"
|
||||
markerWidth="6" markerHeight="6"
|
||||
orient="auto-start-reverse">
|
||||
<polygon points="0 0 10 5 0 10" />
|
||||
</marker>
|
||||
<marker id="deny-dot" viewBox="-2 -2 15 15" refX="6" refY="6" markerWidth="6" markerHeight="6">
|
||||
<circle
|
||||
cx="6"
|
||||
cy="6"
|
||||
r="6"
|
||||
/>
|
||||
</marker>
|
||||
<marker id="deny-arrow" viewBox="-1 -1 12 12" refX="5" refY="5"
|
||||
markerWidth="6" markerHeight="6"
|
||||
orient="auto-start-reverse">
|
||||
<polygon points="0 0 10 5 0 10" />
|
||||
</marker>
|
||||
</defs>
|
||||
{{#each @lines as |line|}}
|
||||
{{#if (eq line.permission 'deny')}}
|
||||
<path
|
||||
id={{line.id}}
|
||||
d={{svg-curve line.dest src=line.src}}
|
||||
marker-start="url(#deny-dot)"
|
||||
marker-end="url(#deny-arrow)"
|
||||
data-permission={{line.permission}}
|
||||
/>
|
||||
{{else}}
|
||||
<path
|
||||
id={{line.id}}
|
||||
d={{svg-curve line.dest src=line.src}}
|
||||
marker-start="url(#allow-dot)"
|
||||
marker-end="url(#allow-arrow)"
|
||||
data-permission={{line.permission}}
|
||||
/>
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
</svg>
|
||||
{{/if}}
|
||||
|
||||
<TopologyMetrics::Icon
|
||||
@positions={{this.iconPositions}}
|
||||
@items={{@items}}
|
||||
/>
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
import Component from '@glimmer/component';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { action } from '@ember/object';
|
||||
|
||||
export default class TopoloyMetricsDownLines extends Component {
|
||||
@tracked iconPositions;
|
||||
|
||||
@action
|
||||
getIconPositions() {
|
||||
const view = this.args.view;
|
||||
const lines = [...document.querySelectorAll('#downstream-lines path')];
|
||||
|
||||
this.iconPositions = lines.map(item => {
|
||||
const pathLen = parseFloat(item.getTotalLength());
|
||||
const thirdLen = item.getPointAtLength(Math.ceil(pathLen / 3));
|
||||
|
||||
return {
|
||||
id: item.id,
|
||||
x: thirdLen.x - view.x,
|
||||
y: thirdLen.y - view.y,
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
{{#each @items as |item|}}
|
||||
{{#let (find-by 'id' (concat item.Namespace item.Name) @positions) as |style|}}
|
||||
{{#if (and (not item.Intention.Allowed) (not item.Intention.HasPermissions))}}
|
||||
<span class="deny" style={{{ concat 'top:' style.y 'px;left:' style.x 'px;'}}}>
|
||||
<Tooltip>
|
||||
An intention is set to 'deny' that prohibits these services from connecting.
|
||||
</Tooltip>
|
||||
</span>
|
||||
{{else if item.Intention.HasPermissions}}
|
||||
<span class="L7" style={{{ concat 'top:' style.y 'px;left:' style.x 'px;'}}}>
|
||||
<Tooltip>
|
||||
The intention between these services has Layer 7 permissions, so certain requests may or may not be permitted.
|
||||
</Tooltip>
|
||||
</span>
|
||||
{{/if}}
|
||||
{{/let}}
|
||||
{{/each}}
|
|
@ -12,7 +12,10 @@
|
|||
</span>
|
||||
</div>
|
||||
{{#each @downstreams as |downstream|}}
|
||||
<div class="card">
|
||||
<div class="card"
|
||||
data-permission={{service/intention-permissions downstream}}
|
||||
id="{{downstream.Namespace}}{{downstream.Name}}"
|
||||
>
|
||||
<p>
|
||||
{{downstream.Name}}
|
||||
</p>
|
||||
|
@ -58,34 +61,13 @@
|
|||
</div>
|
||||
</div>
|
||||
<div id="downstream-lines">
|
||||
{{#if (gt this.downLines.length 0)}}
|
||||
<svg
|
||||
viewBox={{concat downView.x ' ' downView.y ' ' downView.width ' ' downView.height}}
|
||||
preserveAspectRatio="none"
|
||||
>
|
||||
<defs>
|
||||
<marker id="dot" viewBox="-2 -2 15 15" refX="6" refY="6" markerWidth="6" markerHeight="6">
|
||||
<circle
|
||||
cx="6"
|
||||
cy="6"
|
||||
r="6"
|
||||
<TopologyMetrics::DownLines
|
||||
@type='downstream'
|
||||
@view={{this.downView}}
|
||||
@center={{this.centerDimensions}}
|
||||
@lines={{this.downLines}}
|
||||
@items={{@downstreams}}
|
||||
/>
|
||||
</marker>
|
||||
<marker id="arrow" viewBox="-1 -1 12 12" refX="5" refY="5"
|
||||
markerWidth="6" markerHeight="6"
|
||||
orient="auto-start-reverse">
|
||||
<polygon points="0 0 10 5 0 10" />
|
||||
</marker>
|
||||
</defs>
|
||||
{{#each this.downLines as |svg| }}
|
||||
<path
|
||||
d={{svg-curve svg.dest src=svg.src}}
|
||||
marker-start="url(#dot)"
|
||||
marker-end="url(#arrow)"
|
||||
/>
|
||||
{{/each}}
|
||||
</svg>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{#if (gt @upstreams.length 0)}}
|
||||
<div id="upstream-column">
|
||||
|
@ -93,7 +75,10 @@
|
|||
<div id="upstream-container">
|
||||
<p>{{dc}}</p>
|
||||
{{#each upstreams as |upstream|}}
|
||||
<div class="card">
|
||||
<div class="card"
|
||||
data-permission={{service/intention-permissions upstream}}
|
||||
id="{{upstream.Namespace}}{{upstream.Name}}"
|
||||
>
|
||||
<p>
|
||||
{{upstream.Name}}
|
||||
</p>
|
||||
|
@ -142,33 +127,12 @@
|
|||
</div>
|
||||
{{/if}}
|
||||
<div id="upstream-lines">
|
||||
{{#if (gt this.upLines.length 0)}}
|
||||
<svg
|
||||
viewBox={{concat this.centerDimensions.x ' ' upView.y ' ' upView.width ' ' upView.height}}
|
||||
preserveAspectRatio="none"
|
||||
>
|
||||
<defs>
|
||||
<marker id="dot" viewBox="-2 -2 15 15" refX="6" refY="6" markerWidth="6" markerHeight="6">
|
||||
<circle
|
||||
cx="6"
|
||||
cy="6"
|
||||
r="6"
|
||||
<TopologyMetrics::UpLines
|
||||
@type='upstream'
|
||||
@view={{this.upView}}
|
||||
@center={{this.centerDimensions}}
|
||||
@lines={{this.upLines}}
|
||||
@items={{@upstreams}}
|
||||
/>
|
||||
</marker>
|
||||
<marker id="arrow" viewBox="-1 -1 12 12" refX="5" refY="5"
|
||||
markerWidth="6" markerHeight="6"
|
||||
orient="auto-start-reverse">
|
||||
<polygon points="0 0 10 5 0 10" />
|
||||
</marker>
|
||||
</defs>
|
||||
{{#each this.upLines as |svg| }}
|
||||
<path
|
||||
d={{svg-curve svg.dest src=svg.src}}
|
||||
marker-start="url(#dot)"
|
||||
marker-end="url(#arrow)"
|
||||
/>
|
||||
{{/each}}
|
||||
</svg>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
|
@ -12,40 +12,56 @@ export default class TopologyMetrics extends Component {
|
|||
|
||||
// =methods
|
||||
drawDownLines(items) {
|
||||
return items.map(item => {
|
||||
const dimensions = item.getBoundingClientRect();
|
||||
const order = ['allow', 'deny'];
|
||||
const dest = {
|
||||
x: this.centerDimensions.x,
|
||||
y: this.centerDimensions.y + this.centerDimensions.height / 4,
|
||||
};
|
||||
|
||||
return items
|
||||
.map(item => {
|
||||
const dimensions = item.getBoundingClientRect();
|
||||
const src = {
|
||||
x: dimensions.x + dimensions.width,
|
||||
y: dimensions.y + dimensions.height / 2,
|
||||
};
|
||||
|
||||
return {
|
||||
id: item.id,
|
||||
permission: item.getAttribute('data-permission'),
|
||||
dest: dest,
|
||||
src: src,
|
||||
};
|
||||
})
|
||||
.sort((a, b) => {
|
||||
return order.indexOf(a.permission) - order.indexOf(b.permission);
|
||||
});
|
||||
}
|
||||
|
||||
drawUpLines(items) {
|
||||
return items.map(item => {
|
||||
const dimensions = item.getBoundingClientRect();
|
||||
const dest = {
|
||||
x: dimensions.x - dimensions.width - 26,
|
||||
y: dimensions.y + dimensions.height / 2,
|
||||
};
|
||||
const order = ['allow', 'deny'];
|
||||
const src = {
|
||||
x: this.centerDimensions.x + 20,
|
||||
y: this.centerDimensions.y + this.centerDimensions.height / 4,
|
||||
};
|
||||
|
||||
return items
|
||||
.map(item => {
|
||||
const dimensions = item.getBoundingClientRect();
|
||||
const dest = {
|
||||
x: dimensions.x - dimensions.width - 26,
|
||||
y: dimensions.y + dimensions.height / 2,
|
||||
};
|
||||
|
||||
return {
|
||||
id: item.id,
|
||||
permission: item.getAttribute('data-permission'),
|
||||
dest: dest,
|
||||
src: src,
|
||||
};
|
||||
})
|
||||
.sort((a, b) => {
|
||||
return order.indexOf(a.permission) - order.indexOf(b.permission);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -94,15 +94,45 @@
|
|||
circle {
|
||||
fill: $white;
|
||||
}
|
||||
polygon {
|
||||
#allow-arrow {
|
||||
fill: $gray-300;
|
||||
stroke-linejoin: round;
|
||||
}
|
||||
path,
|
||||
circle,
|
||||
polygon {
|
||||
#allow-dot,
|
||||
#allow-arrow {
|
||||
stroke: $gray-300;
|
||||
stroke-width: 2;
|
||||
}
|
||||
|
||||
path[data-permission='deny'] {
|
||||
stroke: $red-500;
|
||||
}
|
||||
#deny-dot {
|
||||
stroke: $red-500;
|
||||
stroke-width: 2;
|
||||
}
|
||||
#deny-arrow {
|
||||
fill: $red-500;
|
||||
stroke: $red-500;
|
||||
stroke-linejoin: round;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Icon on SVG Lines
|
||||
#downstream-lines,
|
||||
#upstream-lines {
|
||||
.deny::before {
|
||||
@extend %with-cancel-square-fill-color-mask, %as-pseudo;
|
||||
background-color: $red-500;
|
||||
}
|
||||
.L7::before {
|
||||
@extend %with-layers-mask, %as-pseudo;
|
||||
background-color: $gray-300;
|
||||
}
|
||||
span {
|
||||
transform: translate(-50%,-50%);
|
||||
position: absolute;
|
||||
background-color: $white;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
{{on-window 'resize' (action this.getIconPositions)}}
|
||||
|
||||
{{#if (gt @lines.length 0)}}
|
||||
<svg
|
||||
{{did-insert this.getIconPositions}}
|
||||
{{did-update this.getIconPositions @lines}}
|
||||
viewBox={{concat @center.x ' ' @view.y ' ' @view.width ' ' @view.height}}
|
||||
preserveAspectRatio="none"
|
||||
>
|
||||
<defs>
|
||||
<marker id="allow-dot" viewBox="-2 -2 15 15" refX="6" refY="6" markerWidth="6" markerHeight="6">
|
||||
<circle
|
||||
cx="6"
|
||||
cy="6"
|
||||
r="6"
|
||||
/>
|
||||
</marker>
|
||||
<marker id="allow-arrow" viewBox="-1 -1 12 12" refX="5" refY="5"
|
||||
markerWidth="6" markerHeight="6"
|
||||
orient="auto-start-reverse">
|
||||
<polygon points="0 0 10 5 0 10" />
|
||||
</marker>
|
||||
<marker id="deny-dot" viewBox="-2 -2 15 15" refX="6" refY="6" markerWidth="6" markerHeight="6">
|
||||
<circle
|
||||
cx="6"
|
||||
cy="6"
|
||||
r="6"
|
||||
/>
|
||||
</marker>
|
||||
<marker id="deny-arrow" viewBox="-1 -1 12 12" refX="5" refY="5"
|
||||
markerWidth="6" markerHeight="6"
|
||||
orient="auto-start-reverse">
|
||||
<polygon points="0 0 10 5 0 10" />
|
||||
</marker>
|
||||
</defs>
|
||||
{{#each @lines as |line|}}
|
||||
{{#if (eq line.permission 'deny')}}
|
||||
<path
|
||||
id={{line.id}}
|
||||
d={{svg-curve line.dest src=line.src}}
|
||||
marker-start="url(#deny-dot)"
|
||||
marker-end="url(#deny-arrow)"
|
||||
data-permission={{line.permission}}
|
||||
/>
|
||||
{{else}}
|
||||
<path
|
||||
id={{line.id}}
|
||||
d={{svg-curve line.dest src=line.src}}
|
||||
marker-start="url(#allow-dot)"
|
||||
marker-end="url(#allow-arrow)"
|
||||
data-permission={{line.permission}}
|
||||
/>
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
</svg>
|
||||
{{/if}}
|
||||
|
||||
<TopologyMetrics::Icon
|
||||
@positions={{this.iconPositions}}
|
||||
@items={{@items}}
|
||||
/>
|
|
@ -0,0 +1,23 @@
|
|||
import Component from '@glimmer/component';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { action } from '@ember/object';
|
||||
|
||||
export default class TopoloyMetricsUpLines extends Component {
|
||||
@tracked iconPositions;
|
||||
|
||||
@action
|
||||
getIconPositions() {
|
||||
const center = this.args.center;
|
||||
const lines = [...document.querySelectorAll('#upstream-lines path')];
|
||||
|
||||
this.iconPositions = lines.map(item => {
|
||||
const pathLen = parseFloat(item.getTotalLength());
|
||||
const partLen = item.getPointAtLength(Math.ceil(pathLen * 0.666));
|
||||
return {
|
||||
id: item.id,
|
||||
x: partLen.x - center.x,
|
||||
y: partLen.y - center.y * 0.81,
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
import { helper } from '@ember/component/helper';
|
||||
|
||||
export default helper(function serviceIntentionPermissions([params] /*, hash*/) {
|
||||
const hasPermissions = params.Intention.HasPermissions;
|
||||
const allowed = params.Intention.Allowed;
|
||||
|
||||
switch (true) {
|
||||
case hasPermissions:
|
||||
return 'allow';
|
||||
case !allowed && !hasPermissions:
|
||||
return 'deny';
|
||||
default:
|
||||
return 'allow';
|
||||
}
|
||||
});
|
|
@ -56,7 +56,7 @@
|
|||
"@ember/render-modifiers": "^1.0.2",
|
||||
"@glimmer/component": "^1.0.0",
|
||||
"@glimmer/tracking": "^1.0.0",
|
||||
"@hashicorp/consul-api-double": "^5.2.3",
|
||||
"@hashicorp/consul-api-double": "^5.3.5",
|
||||
"@hashicorp/ember-cli-api-double": "^3.1.0",
|
||||
"@xstate/fsm": "^1.4.0",
|
||||
"babel-eslint": "^10.0.3",
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
import { module, test } from 'qunit';
|
||||
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) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
// TODO: Replace this with your real tests.
|
||||
test('it renders', async function(assert) {
|
||||
this.set('inputValue', {
|
||||
Intention: {
|
||||
Allowed: false,
|
||||
HasPermissions: true,
|
||||
},
|
||||
});
|
||||
|
||||
await render(hbs`{{service/intention-permissions inputValue}}`);
|
||||
|
||||
assert.equal(this.element.textContent.trim(), 'allow');
|
||||
});
|
||||
});
|
|
@ -1527,10 +1527,10 @@
|
|||
faker "^4.1.0"
|
||||
js-yaml "^3.13.1"
|
||||
|
||||
"@hashicorp/consul-api-double@^5.2.3":
|
||||
version "5.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@hashicorp/consul-api-double/-/consul-api-double-5.2.3.tgz#c34cec063b519595c49bb3fce799541f7d967f66"
|
||||
integrity sha512-NlnBUHoXLlQwTB1lFzYvaIUZnf5KOGnohXRm4D3B8xVC+D0py6dTP5dj3NpBuxrG5b0xSv2zTF3tz9Y5nehOzQ==
|
||||
"@hashicorp/consul-api-double@^5.3.5":
|
||||
version "5.3.5"
|
||||
resolved "https://registry.yarnpkg.com/@hashicorp/consul-api-double/-/consul-api-double-5.3.5.tgz#8e39d6af4ab6d32c7d8c469bb4aab23e16971bd3"
|
||||
integrity sha512-SiT2lLk0J8CwsxtuAobrweC5VdOT6b66M1gSLcT/Lcx62fOLH1X/DfMt6F2VKwC4BN8WBFZGTmn0rwdFOjKpmw==
|
||||
|
||||
"@hashicorp/ember-cli-api-double@^3.1.0":
|
||||
version "3.1.2"
|
||||
|
|
Loading…
Reference in New Issue