diff --git a/ui-v2/app/components/topology-metrics/down-lines/index.hbs b/ui-v2/app/components/topology-metrics/down-lines/index.hbs new file mode 100644 index 0000000000..b1ab5a5e08 --- /dev/null +++ b/ui-v2/app/components/topology-metrics/down-lines/index.hbs @@ -0,0 +1,62 @@ +{{on-window 'resize' (action this.getIconPositions)}} + +{{#if (gt @lines.length 0)}} + + + + + + + + + + + + + + + +{{#each @lines as |line|}} + {{#if (eq line.permission 'deny')}} + + {{else}} + + {{/if}} +{{/each}} + +{{/if}} + + + diff --git a/ui-v2/app/components/topology-metrics/down-lines/index.js b/ui-v2/app/components/topology-metrics/down-lines/index.js new file mode 100644 index 0000000000..1d13e78ad4 --- /dev/null +++ b/ui-v2/app/components/topology-metrics/down-lines/index.js @@ -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, + }; + }); + } +} diff --git a/ui-v2/app/components/topology-metrics/icon.hbs b/ui-v2/app/components/topology-metrics/icon.hbs new file mode 100644 index 0000000000..df75295bd4 --- /dev/null +++ b/ui-v2/app/components/topology-metrics/icon.hbs @@ -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))}} + + + An intention is set to 'deny' that prohibits these services from connecting. + + + {{else if item.Intention.HasPermissions}} + + + The intention between these services has Layer 7 permissions, so certain requests may or may not be permitted. + + + {{/if}} +{{/let}} +{{/each}} \ No newline at end of file diff --git a/ui-v2/app/components/topology-metrics/index.hbs b/ui-v2/app/components/topology-metrics/index.hbs index 09c367b22a..0a7a72bf58 100644 --- a/ui-v2/app/components/topology-metrics/index.hbs +++ b/ui-v2/app/components/topology-metrics/index.hbs @@ -12,7 +12,10 @@ {{#each @downstreams as |downstream|}} -
+

{{downstream.Name}}

@@ -58,34 +61,13 @@
- {{#if (gt this.downLines.length 0)}} - - - - - - - - - - {{#each this.downLines as |svg| }} - - {{/each}} - - {{/if}} +
{{#if (gt @upstreams.length 0)}}
@@ -93,7 +75,10 @@

{{dc}}

{{#each upstreams as |upstream|}} -
+

{{upstream.Name}}

@@ -142,33 +127,12 @@
{{/if}}
- {{#if (gt this.upLines.length 0)}} - - - - - - - - - - {{#each this.upLines as |svg| }} - - {{/each}} - - {{/if}} +
\ No newline at end of file diff --git a/ui-v2/app/components/topology-metrics/index.js b/ui-v2/app/components/topology-metrics/index.js index b127767573..5faede7bba 100644 --- a/ui-v2/app/components/topology-metrics/index.js +++ b/ui-v2/app/components/topology-metrics/index.js @@ -12,41 +12,57 @@ export default class TopologyMetrics extends Component { // =methods drawDownLines(items) { - return items.map(item => { - const dimensions = item.getBoundingClientRect(); - const dest = { - x: this.centerDimensions.x, - y: this.centerDimensions.y + this.centerDimensions.height / 4, - }; - const src = { - x: dimensions.x + dimensions.width, - y: dimensions.y + dimensions.height / 2, - }; + const order = ['allow', 'deny']; + const dest = { + x: this.centerDimensions.x, + y: this.centerDimensions.y + this.centerDimensions.height / 4, + }; - return { - dest: dest, - src: src, - }; - }); + 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 src = { - x: this.centerDimensions.x + 20, - y: this.centerDimensions.y + this.centerDimensions.height / 4, - }; + const order = ['allow', 'deny']; + const src = { + x: this.centerDimensions.x + 20, + y: this.centerDimensions.y + this.centerDimensions.height / 4, + }; - return { - dest: dest, - src: src, - }; - }); + 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); + }); } // =actions diff --git a/ui-v2/app/components/topology-metrics/skin.scss b/ui-v2/app/components/topology-metrics/skin.scss index 4bcb067032..cc10a72113 100644 --- a/ui-v2/app/components/topology-metrics/skin.scss +++ b/ui-v2/app/components/topology-metrics/skin.scss @@ -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; + } +} \ No newline at end of file diff --git a/ui-v2/app/components/topology-metrics/up-lines/index.hbs b/ui-v2/app/components/topology-metrics/up-lines/index.hbs new file mode 100644 index 0000000000..4987ce8211 --- /dev/null +++ b/ui-v2/app/components/topology-metrics/up-lines/index.hbs @@ -0,0 +1,61 @@ +{{on-window 'resize' (action this.getIconPositions)}} + +{{#if (gt @lines.length 0)}} + + + + + + + + + + + + + + + +{{#each @lines as |line|}} + {{#if (eq line.permission 'deny')}} + + {{else}} + + {{/if}} +{{/each}} + +{{/if}} + + diff --git a/ui-v2/app/components/topology-metrics/up-lines/index.js b/ui-v2/app/components/topology-metrics/up-lines/index.js new file mode 100644 index 0000000000..81544cfb82 --- /dev/null +++ b/ui-v2/app/components/topology-metrics/up-lines/index.js @@ -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, + }; + }); + } +} diff --git a/ui-v2/app/helpers/service/intention-permissions.js b/ui-v2/app/helpers/service/intention-permissions.js new file mode 100644 index 0000000000..b4a38f3838 --- /dev/null +++ b/ui-v2/app/helpers/service/intention-permissions.js @@ -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'; + } +}); diff --git a/ui-v2/package.json b/ui-v2/package.json index 5719f45b10..b245e2022b 100644 --- a/ui-v2/package.json +++ b/ui-v2/package.json @@ -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", diff --git a/ui-v2/tests/integration/helpers/service/intention-permissions-test.js b/ui-v2/tests/integration/helpers/service/intention-permissions-test.js new file mode 100644 index 0000000000..6219c04deb --- /dev/null +++ b/ui-v2/tests/integration/helpers/service/intention-permissions-test.js @@ -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'); + }); +}); diff --git a/ui-v2/yarn.lock b/ui-v2/yarn.lock index 68f381a8c1..8f7d5bbd08 100644 --- a/ui-v2/yarn.lock +++ b/ui-v2/yarn.lock @@ -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"