ui: Topology Intentions Popovers (#9137)
* Refactor grid styling for Topology page * Crate TopologyMetrics Button component and move styling * Create intention ID * fixup button styling * Return a link to the create intention page * Rename Button to Popover component * Fixup serializer test * ui: Inline Topology Intention Actions (#9153) * Add arrow and dot to/from metrics back in * Add addional space to have metrics wrap and show in smaller screens * Move logic for finding positioning * Use color variables Co-authored-by: John Cowen <johncowen@users.noreply.github.com>
This commit is contained in:
parent
ed56d62ec2
commit
285962b25b
|
@ -1,11 +1,10 @@
|
||||||
{{#each @items as |item|}}
|
|
||||||
<a class="card"
|
<a class="card"
|
||||||
href={{href-to "dc.services.show.index" item.Name}}
|
href={{href-to "dc.services.show.index" @item.Name}}
|
||||||
data-permission={{service/intention-permissions item}}
|
data-permission={{service/intention-permissions @item}}
|
||||||
id="{{item.Namespace}}{{item.Name}}"
|
id="{{@item.Namespace}}{{@item.Name}}"
|
||||||
>
|
>
|
||||||
<p>
|
<p>
|
||||||
{{item.Name}}
|
{{@item.Name}}
|
||||||
</p>
|
</p>
|
||||||
<div class="details">
|
<div class="details">
|
||||||
{{#if (and (and nspace (env 'CONSUL_NSPACES_ENABLED')) @type)}}
|
{{#if (and (and nspace (env 'CONSUL_NSPACES_ENABLED')) @type)}}
|
||||||
|
@ -16,12 +15,12 @@
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</dt>
|
</dt>
|
||||||
<dd>
|
<dd>
|
||||||
{{item.Namespace}}
|
{{@item.Namespace}}
|
||||||
</dd>
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#if (eq item.Datacenter @dc)}}
|
{{#if (eq @item.Datacenter @dc)}}
|
||||||
{{#let (service/health-percentage item) as |percentage|}}
|
{{#let (service/health-percentage @item) as |percentage|}}
|
||||||
{{#if (not-eq percentage.passing 0)}}
|
{{#if (not-eq percentage.passing 0)}}
|
||||||
<span class="passing">{{percentage.passing}}%</span>
|
<span class="passing">{{percentage.passing}}%</span>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
@ -50,17 +49,16 @@
|
||||||
<TopologyMetrics::Stats
|
<TopologyMetrics::Stats
|
||||||
@endpoint='upstream-summary-for-service'
|
@endpoint='upstream-summary-for-service'
|
||||||
@service={{@service.Service}}
|
@service={{@service.Service}}
|
||||||
@item={{item.Name}}
|
@item={{@item.Name}}
|
||||||
@noMetricsReason={{@noMetricsReason}}
|
@noMetricsReason={{@noMetricsReason}}
|
||||||
/>
|
/>
|
||||||
{{else}}
|
{{else}}
|
||||||
<TopologyMetrics::Stats
|
<TopologyMetrics::Stats
|
||||||
@endpoint='downstream-summary-for-service'
|
@endpoint='downstream-summary-for-service'
|
||||||
@service={{@service.Service}}
|
@service={{@service.Service}}
|
||||||
@item={{item.Name}}
|
@item={{@item.Name}}
|
||||||
@noMetricsReason={{@noMetricsReason}}
|
@noMetricsReason={{@noMetricsReason}}
|
||||||
/>
|
/>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</a>
|
</a>
|
||||||
{{/each}}
|
|
|
@ -55,8 +55,11 @@
|
||||||
</svg>
|
</svg>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
<TopologyMetrics::Icon
|
{{#each @items as |item|}}
|
||||||
@positions={{this.iconPositions}}
|
<TopologyMetrics::Popover
|
||||||
@items={{@items}}
|
@position={{find-by 'id' (concat item.Namespace item.Name) this.iconPositions}}
|
||||||
|
@item={{item}}
|
||||||
|
@oncreate={{action @oncreate item @service}}
|
||||||
/>
|
/>
|
||||||
|
{{/each}}
|
||||||
|
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
{{#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}}
|
|
|
@ -1,6 +1,10 @@
|
||||||
{{on-window 'resize' (action this.calculate)}}
|
{{on-window 'resize' (action this.calculate)}}
|
||||||
|
|
||||||
<div {{did-insert (action this.calculate)}} {{did-update (action this.calculate) @topology}} class="topology-container">
|
<div
|
||||||
|
{{did-insert (action this.calculate)}}
|
||||||
|
{{did-update (action this.calculate) @topology.Upstreams @topology.Downstreams}}
|
||||||
|
class="topology-container"
|
||||||
|
>
|
||||||
{{#if (gt @topology.Downstreams.length 0)}}
|
{{#if (gt @topology.Downstreams.length 0)}}
|
||||||
<div id="downstream-container">
|
<div id="downstream-container">
|
||||||
<div>
|
<div>
|
||||||
|
@ -11,13 +15,15 @@
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
{{#each @topology.Downstreams as |item|}}
|
||||||
<TopologyMetrics::Card
|
<TopologyMetrics::Card
|
||||||
@items={{@topology.Downstreams}}
|
@item={{item}}
|
||||||
@service={{@service.Service}}
|
@service={{@service.Service}}
|
||||||
@dc={{@topology.Datacenter}}
|
@dc={{@topology.Datacenter}}
|
||||||
@hasMetricsProvider={{this.hasMetricsProvider}}
|
@hasMetricsProvider={{this.hasMetricsProvider}}
|
||||||
@noMetricsReason={{this.noMetricsReason}}
|
@noMetricsReason={{this.noMetricsReason}}
|
||||||
/>
|
/>
|
||||||
|
{{/each}}
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
<div id="metrics-container">
|
<div id="metrics-container">
|
||||||
|
@ -52,10 +58,12 @@
|
||||||
<div id="downstream-lines">
|
<div id="downstream-lines">
|
||||||
<TopologyMetrics::DownLines
|
<TopologyMetrics::DownLines
|
||||||
@type='downstream'
|
@type='downstream'
|
||||||
|
@service={{@service}}
|
||||||
@view={{this.downView}}
|
@view={{this.downView}}
|
||||||
@center={{this.centerDimensions}}
|
@center={{this.centerDimensions}}
|
||||||
@lines={{this.downLines}}
|
@lines={{this.downLines}}
|
||||||
@items={{@topology.Downstreams}}
|
@items={{@topology.Downstreams}}
|
||||||
|
@oncreate={{action @oncreate}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{{#if (gt @topology.Upstreams.length 0)}}
|
{{#if (gt @topology.Upstreams.length 0)}}
|
||||||
|
@ -63,14 +71,16 @@
|
||||||
{{#each-in (group-by "Datacenter" @topology.Upstreams) as |dc upstreams|}}
|
{{#each-in (group-by "Datacenter" @topology.Upstreams) as |dc upstreams|}}
|
||||||
<div id="upstream-container">
|
<div id="upstream-container">
|
||||||
<p>{{dc}}</p>
|
<p>{{dc}}</p>
|
||||||
|
{{#each upstreams as |item|}}
|
||||||
<TopologyMetrics::Card
|
<TopologyMetrics::Card
|
||||||
@items={{upstreams}}
|
@item={{item}}
|
||||||
@service={{@service.Service}}
|
@service={{@service.Service}}
|
||||||
@dc={{@topology.Datacenter}}
|
@dc={{@topology.Datacenter}}
|
||||||
@type='upstream'
|
@type='upstream'
|
||||||
@hasMetricsProvider={{this.hasMetricsProvider}}
|
@hasMetricsProvider={{this.hasMetricsProvider}}
|
||||||
@noMetricsReason={{this.noMetricsReason}}
|
@noMetricsReason={{this.noMetricsReason}}
|
||||||
/>
|
/>
|
||||||
|
{{/each}}
|
||||||
</div>
|
</div>
|
||||||
{{/each-in}}
|
{{/each-in}}
|
||||||
</div>
|
</div>
|
||||||
|
@ -78,10 +88,12 @@
|
||||||
<div id="upstream-lines">
|
<div id="upstream-lines">
|
||||||
<TopologyMetrics::UpLines
|
<TopologyMetrics::UpLines
|
||||||
@type='upstream'
|
@type='upstream'
|
||||||
|
@service={{@service}}
|
||||||
@view={{this.upView}}
|
@view={{this.upView}}
|
||||||
@center={{this.centerDimensions}}
|
@center={{this.centerDimensions}}
|
||||||
@lines={{this.upLines}}
|
@lines={{this.upLines}}
|
||||||
@items={{@topology.Upstreams}}
|
@items={{@topology.Upstreams}}
|
||||||
|
@oncreate={{action @oncreate}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
|
@ -38,7 +38,7 @@ export default class TopologyMetrics extends Component {
|
||||||
drawDownLines(items) {
|
drawDownLines(items) {
|
||||||
const order = ['allow', 'deny'];
|
const order = ['allow', 'deny'];
|
||||||
const dest = {
|
const dest = {
|
||||||
x: this.centerDimensions.x,
|
x: this.centerDimensions.x - 7,
|
||||||
y: this.centerDimensions.y + this.centerDimensions.height / 2,
|
y: this.centerDimensions.y + this.centerDimensions.height / 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -65,7 +65,7 @@ export default class TopologyMetrics extends Component {
|
||||||
drawUpLines(items) {
|
drawUpLines(items) {
|
||||||
const order = ['allow', 'deny'];
|
const order = ['allow', 'deny'];
|
||||||
const src = {
|
const src = {
|
||||||
x: this.centerDimensions.x + 20,
|
x: this.centerDimensions.x + 5.5,
|
||||||
y: this.centerDimensions.y + this.centerDimensions.height / 2,
|
y: this.centerDimensions.y + this.centerDimensions.height / 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -73,7 +73,7 @@ export default class TopologyMetrics extends Component {
|
||||||
.map(item => {
|
.map(item => {
|
||||||
const dimensions = item.getBoundingClientRect();
|
const dimensions = item.getBoundingClientRect();
|
||||||
const dest = {
|
const dest = {
|
||||||
x: dimensions.x - dimensions.width - 26,
|
x: dimensions.x - dimensions.width - 25,
|
||||||
y: dimensions.y + dimensions.height / 2,
|
y: dimensions.y + dimensions.height / 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -2,35 +2,40 @@
|
||||||
display: grid;
|
display: grid;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
align-items: start;
|
align-items: start;
|
||||||
grid-template-columns: 2fr 20px 1fr 20px 2fr 20px 1fr 20px 2fr;
|
grid-template-columns: 2fr 1fr 2fr 1fr 2fr;
|
||||||
grid-template-rows: 50px 1fr 50px;
|
grid-template-rows: 50px 1fr 50px;
|
||||||
|
grid-template-areas:
|
||||||
|
"down-cards down-lines . up-lines up-cards"
|
||||||
|
"down-cards down-lines metrics up-lines up-cards"
|
||||||
|
"down-cards down-lines . up-lines up-cards";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Grid Layout
|
// Grid Layout
|
||||||
#downstream-container {
|
#downstream-container {
|
||||||
grid-row: 1 / 3;
|
grid-area: down-cards;
|
||||||
grid-column: 1 / 3;
|
|
||||||
}
|
}
|
||||||
#downstream-lines {
|
#downstream-lines {
|
||||||
grid-row: 1 / 3;
|
grid-area: down-lines;
|
||||||
grid-column: 2 / 5;
|
|
||||||
}
|
}
|
||||||
#upstream-lines {
|
#upstream-lines {
|
||||||
grid-row: 1 / 3;
|
grid-area: up-lines;
|
||||||
grid-column: 6 / 9;
|
|
||||||
}
|
}
|
||||||
#upstream-column {
|
#upstream-column {
|
||||||
grid-row: 1 / 3;
|
grid-area: up-cards;
|
||||||
grid-column: 8 / 10;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Columns/Containers & Lines
|
// Columns/Containers & Lines
|
||||||
#downstream-lines,
|
#downstream-lines,
|
||||||
#upstream-lines {
|
#upstream-lines {
|
||||||
z-index: 1;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
#downstream-lines {
|
||||||
|
margin-left: -20px;
|
||||||
|
}
|
||||||
|
#upstream-lines {
|
||||||
|
margin-right: -20px;
|
||||||
|
}
|
||||||
#downstream-container,
|
#downstream-container,
|
||||||
#upstream-container {
|
#upstream-container {
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
|
@ -86,8 +91,7 @@
|
||||||
|
|
||||||
// Metrics Container
|
// Metrics Container
|
||||||
#metrics-container {
|
#metrics-container {
|
||||||
grid-row: 2 / 3;
|
grid-area: metrics;
|
||||||
grid-column: 4 / 7;
|
|
||||||
|
|
||||||
div:first-child {
|
div:first-child {
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
|
|
|
@ -0,0 +1,71 @@
|
||||||
|
<div class="topology-metrics-popover">
|
||||||
|
{{#if (and (not @item.Intention.Allowed) (not @item.Intention.HasPermissions))}}
|
||||||
|
<button
|
||||||
|
{{on "click" this.togglePopover}}
|
||||||
|
type="button"
|
||||||
|
class="deny-target"
|
||||||
|
style={{{ concat 'top:' @position.y 'px;left:' @position.x 'px;'}}}
|
||||||
|
>
|
||||||
|
<EmberPopover
|
||||||
|
@isShown={{this.showToggleablePopover}}
|
||||||
|
@event="none"
|
||||||
|
@hideOn="mouseleave"
|
||||||
|
@side="bottom-start"
|
||||||
|
@tooltipClass="deny-popover"
|
||||||
|
>
|
||||||
|
<div class="body">
|
||||||
|
<h3>Connection Denied</h3>
|
||||||
|
<p>Add an intention that allows these two services to connect.</p>
|
||||||
|
</div>
|
||||||
|
<div class="actions">
|
||||||
|
{{#if @item.Intention.HasExact}}
|
||||||
|
<a href={{href-to 'dc.services.show.intentions.edit' (concat @item.Intention.ID)}}>Edit</a>
|
||||||
|
{{else}}
|
||||||
|
<button
|
||||||
|
{{on "click" @oncreate}}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
Create
|
||||||
|
</button>
|
||||||
|
{{/if}}
|
||||||
|
<button
|
||||||
|
{{on "click" this.togglePopover}}
|
||||||
|
class="cancel"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</EmberPopover>
|
||||||
|
</button>
|
||||||
|
{{else if @item.Intention.HasPermissions}}
|
||||||
|
<button
|
||||||
|
{{on "click" this.togglePopover}}
|
||||||
|
type="button"
|
||||||
|
class="L7-target"
|
||||||
|
style={{{ concat 'top:' @position.y 'px;left:' @position.x 'px;'}}}
|
||||||
|
>
|
||||||
|
<EmberPopover
|
||||||
|
@isShown={{this.showToggleablePopover}}
|
||||||
|
@event="none"
|
||||||
|
@hideOn="mouseleave"
|
||||||
|
@side="bottom-start"
|
||||||
|
@tooltipClass="L7-popover"
|
||||||
|
>
|
||||||
|
<div class="body">
|
||||||
|
<h3>Layer 7 permissions</h3>
|
||||||
|
<p>Certain HTTP request info must be identified.</p>
|
||||||
|
</div>
|
||||||
|
<div class="actions">
|
||||||
|
<a href={{href-to 'dc.services.show.intentions.edit' (concat @item.Intention.ID)}}>View</a>
|
||||||
|
<button
|
||||||
|
{{on "click" this.togglePopover}}
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</EmberPopover>
|
||||||
|
</button>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
import Component from '@glimmer/component';
|
||||||
|
import { action } from '@ember/object';
|
||||||
|
import { tracked } from '@glimmer/tracking';
|
||||||
|
|
||||||
|
export default class TopoloyMetricsButton extends Component {
|
||||||
|
// =attributes
|
||||||
|
@tracked showToggleablePopover = false;
|
||||||
|
|
||||||
|
// =actions
|
||||||
|
@action
|
||||||
|
togglePopover() {
|
||||||
|
this.showToggleablePopover = !this.showToggleablePopover;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,87 @@
|
||||||
|
.topology-metrics-popover {
|
||||||
|
.deny-target,
|
||||||
|
.L7-target {
|
||||||
|
transform: translate(-50%,-50%);
|
||||||
|
position: absolute;
|
||||||
|
background-color: $white;
|
||||||
|
padding: 1px 2px;
|
||||||
|
}
|
||||||
|
.deny-target:hover,
|
||||||
|
.L7-target:hover {
|
||||||
|
cursor:pointer;
|
||||||
|
}
|
||||||
|
.deny-target:active,
|
||||||
|
.deny-target:focus,
|
||||||
|
.L7-target:active,
|
||||||
|
.L7-target:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
.deny-target::before {
|
||||||
|
@extend %with-cancel-square-fill-color-mask, %as-pseudo;
|
||||||
|
background-color: $red-500;
|
||||||
|
}
|
||||||
|
.L7-target::before {
|
||||||
|
@extend %with-layers-mask, %as-pseudo;
|
||||||
|
background-color: $gray-300;
|
||||||
|
}
|
||||||
|
.body {
|
||||||
|
padding: 12px 12px 16px 25px;
|
||||||
|
h3 {
|
||||||
|
font-size: 14px;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.actions {
|
||||||
|
border-top: 1px solid $gray-300;
|
||||||
|
width: 100%;
|
||||||
|
display: inline-flex;
|
||||||
|
a,
|
||||||
|
button {
|
||||||
|
width: 50%;
|
||||||
|
height: 36px;
|
||||||
|
padding: 10px 0;
|
||||||
|
font-weight: $typo-weight-medium;
|
||||||
|
text-align: center;
|
||||||
|
line-height: normal;
|
||||||
|
}
|
||||||
|
button:first-child {
|
||||||
|
color: $blue-500;
|
||||||
|
}
|
||||||
|
button:nth-child(2) {
|
||||||
|
color: $gray-800;
|
||||||
|
}
|
||||||
|
button:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.ember-popover {
|
||||||
|
padding: 0;
|
||||||
|
width: 200px;
|
||||||
|
z-index: 3 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.L7-popover {
|
||||||
|
.body {
|
||||||
|
background-color: $white;
|
||||||
|
}
|
||||||
|
h3 {
|
||||||
|
margin-left: -20px;
|
||||||
|
color: $blue-500;
|
||||||
|
}
|
||||||
|
h3::before {
|
||||||
|
@extend %with-info-circle-fill-mask, %as-pseudo;
|
||||||
|
color: $blue-500;
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.deny-popover {
|
||||||
|
.body {
|
||||||
|
background-color: $white;
|
||||||
|
}
|
||||||
|
h3 {
|
||||||
|
color: $red-800;
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,7 +4,6 @@
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
height: 70px;
|
height: 70px;
|
||||||
z-index: 2;
|
|
||||||
|
|
||||||
svg.sparkline {
|
svg.sparkline {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -16,7 +15,7 @@
|
||||||
.tooltip {
|
.tooltip {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 100;
|
z-index: 10;
|
||||||
bottom: 78px;
|
bottom: 78px;
|
||||||
width: 217px;
|
width: 217px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -123,22 +123,3 @@
|
||||||
stroke-linejoin: round;
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,15 +1,13 @@
|
||||||
.stats {
|
.stats {
|
||||||
padding: 12px;
|
padding: 12px 12px 0 12px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-flow: row wrap;
|
flex-flow: row wrap;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 46px;
|
|
||||||
overflow: hidden;
|
|
||||||
dl {
|
dl {
|
||||||
display:flex;
|
display:flex;
|
||||||
margin-bottom: 50px; // pushes wrapped metrics well out of the bounding box to hide them.
|
padding-bottom: 12px;
|
||||||
}
|
}
|
||||||
dt {
|
dt {
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
|
|
|
@ -55,7 +55,10 @@
|
||||||
</svg>
|
</svg>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
<TopologyMetrics::Icon
|
{{#each @items as |item|}}
|
||||||
@positions={{this.iconPositions}}
|
<TopologyMetrics::Popover
|
||||||
@items={{@items}}
|
@position={{find-by 'id' (concat item.Namespace item.Name) this.iconPositions}}
|
||||||
|
@item={{item}}
|
||||||
|
@oncreate={{action @oncreate @service item}}
|
||||||
/>
|
/>
|
||||||
|
{{/each}}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import Component from '@glimmer/component';
|
||||||
import { tracked } from '@glimmer/tracking';
|
import { tracked } from '@glimmer/tracking';
|
||||||
import { action } from '@ember/object';
|
import { action } from '@ember/object';
|
||||||
|
|
||||||
export default class TopoloyMetricsUpLines extends Component {
|
export default class TopologyMetricsUpLines extends Component {
|
||||||
@tracked iconPositions;
|
@tracked iconPositions;
|
||||||
|
|
||||||
@action
|
@action
|
||||||
|
|
|
@ -2,13 +2,27 @@ import { inject as service } from '@ember/service';
|
||||||
import Route from 'consul-ui/routing/route';
|
import Route from 'consul-ui/routing/route';
|
||||||
import { hash } from 'rsvp';
|
import { hash } from 'rsvp';
|
||||||
import { get } from '@ember/object';
|
import { get } from '@ember/object';
|
||||||
|
import { action, setProperties } from '@ember/object';
|
||||||
|
|
||||||
export default class ShowRoute extends Route {
|
export default class ShowRoute extends Route {
|
||||||
@service('data-source/service')
|
@service('data-source/service') data;
|
||||||
data;
|
@service('repository/intention') repo;
|
||||||
|
@service('ui-config') config;
|
||||||
|
|
||||||
@service('ui-config')
|
@action
|
||||||
config;
|
async createIntention(source, destination) {
|
||||||
|
const intention = service.Intention;
|
||||||
|
const model = this.repo.create({
|
||||||
|
Datacenter: source.Datacenter,
|
||||||
|
SourceName: source.Name,
|
||||||
|
SourceNS: source.Namespace || 'default',
|
||||||
|
DestinationName: destination.Name,
|
||||||
|
DestinationNS: destination.Namespace || 'default',
|
||||||
|
Action: 'allow',
|
||||||
|
});
|
||||||
|
await this.repo.persist(model);
|
||||||
|
this.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
model(params, transition) {
|
model(params, transition) {
|
||||||
const dc = this.modelFor('dc').dc.Name;
|
const dc = this.modelFor('dc').dc.Name;
|
||||||
|
|
|
@ -1,13 +1,31 @@
|
||||||
import Serializer from './application';
|
import Serializer from './application';
|
||||||
import { PRIMARY_KEY, SLUG_KEY } from 'consul-ui/models/topology';
|
import { PRIMARY_KEY, SLUG_KEY } from 'consul-ui/models/topology';
|
||||||
|
import { inject as service } from '@ember/service';
|
||||||
|
|
||||||
export default class TopologySerializer extends Serializer {
|
export default class TopologySerializer extends Serializer {
|
||||||
|
@service('store') store;
|
||||||
|
|
||||||
primaryKey = PRIMARY_KEY;
|
primaryKey = PRIMARY_KEY;
|
||||||
slugKey = SLUG_KEY;
|
slugKey = SLUG_KEY;
|
||||||
|
|
||||||
respondForQueryRecord(respond, query) {
|
respondForQueryRecord(respond, query) {
|
||||||
|
const intentionSerializer = this.store.serializerFor('intention');
|
||||||
return super.respondForQueryRecord(function(cb) {
|
return super.respondForQueryRecord(function(cb) {
|
||||||
return respond(function(headers, body) {
|
return respond(function(headers, body) {
|
||||||
|
body.Downstreams.forEach(item => {
|
||||||
|
item.Intention.SourceName = item.Name;
|
||||||
|
item.Intention.SourceNS = item.Namespace;
|
||||||
|
item.Intention.DestinationName = query.id;
|
||||||
|
item.Intention.DestinationNS = query.ns || 'default';
|
||||||
|
intentionSerializer.ensureID(item.Intention);
|
||||||
|
});
|
||||||
|
body.Upstreams.forEach(item => {
|
||||||
|
item.Intention.SourceName = query.id;
|
||||||
|
item.Intention.SourceNS = query.ns || 'default';
|
||||||
|
item.Intention.DestinationName = item.Name;
|
||||||
|
item.Intention.DestinationNS = item.Namespace;
|
||||||
|
intentionSerializer.ensureID(item.Intention);
|
||||||
|
});
|
||||||
return cb(headers, {
|
return cb(headers, {
|
||||||
...body,
|
...body,
|
||||||
[SLUG_KEY]: query.id,
|
[SLUG_KEY]: query.id,
|
||||||
|
|
|
@ -62,6 +62,7 @@
|
||||||
@import 'consul-ui/components/consul/intention';
|
@import 'consul-ui/components/consul/intention';
|
||||||
@import 'consul-ui/components/role-selector';
|
@import 'consul-ui/components/role-selector';
|
||||||
@import 'consul-ui/components/topology-metrics';
|
@import 'consul-ui/components/topology-metrics';
|
||||||
|
@import 'consul-ui/components/topology-metrics/popover';
|
||||||
@import 'consul-ui/components/topology-metrics/series';
|
@import 'consul-ui/components/topology-metrics/series';
|
||||||
@import 'consul-ui/components/topology-metrics/stats';
|
@import 'consul-ui/components/topology-metrics/stats';
|
||||||
@import 'consul-ui/components/topology-metrics/status';
|
@import 'consul-ui/components/topology-metrics/status';
|
||||||
|
|
|
@ -11,6 +11,8 @@
|
||||||
Datacenter=dc
|
Datacenter=dc
|
||||||
Service=items.firstObject
|
Service=items.firstObject
|
||||||
)}}
|
)}}
|
||||||
|
|
||||||
|
@oncreate={{route-action 'createIntention'}}
|
||||||
/>
|
/>
|
||||||
{{else}}
|
{{else}}
|
||||||
<EmptyState>
|
<EmptyState>
|
||||||
|
|
|
@ -123,6 +123,7 @@
|
||||||
"ember-ref-modifier": "^1.0.0",
|
"ember-ref-modifier": "^1.0.0",
|
||||||
"ember-render-helpers": "^0.1.1",
|
"ember-render-helpers": "^0.1.1",
|
||||||
"ember-resolver": "^8.0.0",
|
"ember-resolver": "^8.0.0",
|
||||||
|
"ember-route-action-helper": "^2.0.8",
|
||||||
"ember-router-helpers": "^0.4.0",
|
"ember-router-helpers": "^0.4.0",
|
||||||
"ember-sinon-qunit": "5.0.0",
|
"ember-sinon-qunit": "5.0.0",
|
||||||
"ember-source": "~3.20.5",
|
"ember-source": "~3.20.5",
|
||||||
|
|
|
@ -10,8 +10,9 @@ module('Integration | Serializer | topology', function(hooks) {
|
||||||
const serializer = this.owner.lookup('serializer:topology');
|
const serializer = this.owner.lookup('serializer:topology');
|
||||||
const dc = 'dc-1';
|
const dc = 'dc-1';
|
||||||
const id = 'slug';
|
const id = 'slug';
|
||||||
|
const kind = '';
|
||||||
const request = {
|
const request = {
|
||||||
url: `/v1/internal/ui/service-topology/${id}?dc=${dc}`,
|
url: `/v1/internal/ui/service-topology/${id}?dc=${dc}&kind=${kind}`,
|
||||||
};
|
};
|
||||||
return get(request.url).then(function(payload) {
|
return get(request.url).then(function(payload) {
|
||||||
const expected = {
|
const expected = {
|
||||||
|
@ -28,6 +29,7 @@ module('Integration | Serializer | topology', function(hooks) {
|
||||||
{
|
{
|
||||||
dc: dc,
|
dc: dc,
|
||||||
id: id,
|
id: id,
|
||||||
|
kind: kind,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
assert.equal(actual.Datacenter, expected.Datacenter);
|
assert.equal(actual.Datacenter, expected.Datacenter);
|
||||||
|
|
23
ui/yarn.lock
23
ui/yarn.lock
|
@ -8031,6 +8031,13 @@ ember-export-application-global@^2.0.1:
|
||||||
resolved "https://registry.yarnpkg.com/ember-export-application-global/-/ember-export-application-global-2.0.1.tgz#b120a70e322ab208defc9e2daebe8d0dfc2dcd46"
|
resolved "https://registry.yarnpkg.com/ember-export-application-global/-/ember-export-application-global-2.0.1.tgz#b120a70e322ab208defc9e2daebe8d0dfc2dcd46"
|
||||||
integrity sha512-B7wiurPgsxsSGzJuPFkpBWnaeuCu2PGpG2BjyrfA1VcL7//o+5RSnZqiCEY326y7qmxb2GoCgo0ft03KBU0rRw==
|
integrity sha512-B7wiurPgsxsSGzJuPFkpBWnaeuCu2PGpG2BjyrfA1VcL7//o+5RSnZqiCEY326y7qmxb2GoCgo0ft03KBU0rRw==
|
||||||
|
|
||||||
|
ember-factory-for-polyfill@^1.3.1:
|
||||||
|
version "1.3.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/ember-factory-for-polyfill/-/ember-factory-for-polyfill-1.3.1.tgz#b446ed64916d293c847a4955240eb2c993b86eae"
|
||||||
|
integrity sha512-y3iG2iCzH96lZMTWQw6LWNLAfOmDC4pXKbZP6FxG8lt7GGaNFkZjwsf+Z5GAe7kxfD7UG4lVkF7x37K82rySGA==
|
||||||
|
dependencies:
|
||||||
|
ember-cli-version-checker "^2.1.0"
|
||||||
|
|
||||||
ember-get-config@^0.2.4:
|
ember-get-config@^0.2.4:
|
||||||
version "0.2.4"
|
version "0.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/ember-get-config/-/ember-get-config-0.2.4.tgz#118492a2a03d73e46004ed777928942021fe1ecd"
|
resolved "https://registry.yarnpkg.com/ember-get-config/-/ember-get-config-0.2.4.tgz#118492a2a03d73e46004ed777928942021fe1ecd"
|
||||||
|
@ -8039,6 +8046,14 @@ ember-get-config@^0.2.4:
|
||||||
broccoli-file-creator "^1.1.1"
|
broccoli-file-creator "^1.1.1"
|
||||||
ember-cli-babel "^6.3.0"
|
ember-cli-babel "^6.3.0"
|
||||||
|
|
||||||
|
ember-getowner-polyfill@^2.0.0:
|
||||||
|
version "2.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/ember-getowner-polyfill/-/ember-getowner-polyfill-2.2.0.tgz#38e7dccbcac69d5ec694000329ec0b2be651d2b2"
|
||||||
|
integrity sha512-rwGMJgbGzxIAiWYjdpAh04Abvt0s3HuS/VjHzUFhVyVg2pzAuz45B9AzOxYXzkp88vFC7FPaiA4kE8NxNk4A4Q==
|
||||||
|
dependencies:
|
||||||
|
ember-cli-version-checker "^2.1.0"
|
||||||
|
ember-factory-for-polyfill "^1.3.1"
|
||||||
|
|
||||||
ember-href-to@^3.1.0:
|
ember-href-to@^3.1.0:
|
||||||
version "3.1.0"
|
version "3.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/ember-href-to/-/ember-href-to-3.1.0.tgz#704f66c2b555a2685fac9ddc74eb9c95abaf5b8f"
|
resolved "https://registry.yarnpkg.com/ember-href-to/-/ember-href-to-3.1.0.tgz#704f66c2b555a2685fac9ddc74eb9c95abaf5b8f"
|
||||||
|
@ -8222,6 +8237,14 @@ ember-rfc176-data@^0.3.12, ember-rfc176-data@^0.3.13, ember-rfc176-data@^0.3.16:
|
||||||
resolved "https://registry.yarnpkg.com/ember-rfc176-data/-/ember-rfc176-data-0.3.16.tgz#2ace0ac9cf9016d493a74a1d931643a308679803"
|
resolved "https://registry.yarnpkg.com/ember-rfc176-data/-/ember-rfc176-data-0.3.16.tgz#2ace0ac9cf9016d493a74a1d931643a308679803"
|
||||||
integrity sha512-IYAzffS90r2ybAcx8c2qprYfkxa70G+/UPkxMN1hw55DU5S2aLOX6v3umKDZItoRhrvZMCnzwsdfKSrKdC9Wbg==
|
integrity sha512-IYAzffS90r2ybAcx8c2qprYfkxa70G+/UPkxMN1hw55DU5S2aLOX6v3umKDZItoRhrvZMCnzwsdfKSrKdC9Wbg==
|
||||||
|
|
||||||
|
ember-route-action-helper@^2.0.8:
|
||||||
|
version "2.0.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/ember-route-action-helper/-/ember-route-action-helper-2.0.8.tgz#f227fcccb73e839b65e9b814e241b322fe8c02fc"
|
||||||
|
integrity sha512-V+4uKwqaYveriVt2rl4e+9mzHJiQOr1B8dCPQQ2TS3iAcmi5RD2giRDFGtCK9d2XY9Arb/f9hJh0obP20iyt3A==
|
||||||
|
dependencies:
|
||||||
|
ember-cli-babel "^6.8.1"
|
||||||
|
ember-getowner-polyfill "^2.0.0"
|
||||||
|
|
||||||
ember-router-generator@^2.0.0:
|
ember-router-generator@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/ember-router-generator/-/ember-router-generator-2.0.0.tgz#d04abfed4ba8b42d166477bbce47fccc672dbde0"
|
resolved "https://registry.yarnpkg.com/ember-router-generator/-/ember-router-generator-2.0.0.tgz#d04abfed4ba8b42d166477bbce47fccc672dbde0"
|
||||||
|
|
Loading…
Reference in New Issue