ui: Ensure proxy instance health is taken into account in Service Instance Listings (#12279)
We noticed that the Service Instance listing on both Node and Service views where not taking into account proxy instance health. This fixes that up so that the small health check information in each Service Instance row includes the proxy instances health checks when displaying Service Instance health (afterall if the proxy instance is unhealthy then so is the service instance that it should be proxying) * Refactor Consul::InstanceChecks with docs * Add to-hash helper, which will return an object keyed by a prop * Stop using/relying on ember-data type things, just use a hash lookup * For the moment add an equivalent "just give me proxies" model prop * Start stitching things together, this one requires an extra HTTP request ..previously we weren't even requesting proxies instances here * Finish up the stitching * Document Consul::ServiceInstance::List while I'm here * Fix up navigation mocks Name > Service
This commit is contained in:
parent
f6d233e4aa
commit
d3e5d6edc4
|
@ -0,0 +1,3 @@
|
|||
```release-note:bugfix
|
||||
ui: Ensure proxy instance health is taken into account in Service Instance Listings
|
||||
```
|
|
@ -0,0 +1,96 @@
|
|||
# Consul::InstanceChecks
|
||||
|
||||
A presentational component to show an overview/summary of service or node
|
||||
health.
|
||||
|
||||
```hbs preview-template
|
||||
<figure>
|
||||
<figcaption>With no checks</figcaption>
|
||||
<Consul::InstanceChecks
|
||||
@type="service"
|
||||
@items={{array}}
|
||||
/>
|
||||
</figure>
|
||||
<figure>
|
||||
<figcaption>With all passing check</figcaption>
|
||||
<Consul::InstanceChecks
|
||||
@type="service"
|
||||
@items={{array
|
||||
(hash
|
||||
Status="passing"
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</figure>
|
||||
<figure>
|
||||
<figcaption>With a warning check</figcaption>
|
||||
<Consul::InstanceChecks
|
||||
@type="service"
|
||||
@items={{array
|
||||
(hash
|
||||
Status="passing"
|
||||
)
|
||||
(hash
|
||||
Status="passing"
|
||||
)
|
||||
(hash
|
||||
Status="warning"
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</figure>
|
||||
<figure>
|
||||
<figcaption>With a critical check</figcaption>
|
||||
<Consul::InstanceChecks
|
||||
@type="service"
|
||||
@items={{array
|
||||
(hash
|
||||
Status="passing"
|
||||
)
|
||||
(hash
|
||||
Status="warning"
|
||||
)
|
||||
(hash
|
||||
Status="warning"
|
||||
)
|
||||
(hash
|
||||
Status="critical"
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</figure>
|
||||
<figure>
|
||||
<figcaption>Nodes with a critical check</figcaption>
|
||||
<Consul::InstanceChecks
|
||||
@type="node"
|
||||
@items={{array
|
||||
(hash
|
||||
Status="passing"
|
||||
)
|
||||
(hash
|
||||
Status="warning"
|
||||
)
|
||||
(hash
|
||||
Status="warning"
|
||||
)
|
||||
(hash
|
||||
Status="critical"
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</figure>
|
||||
```
|
||||
|
||||
## Arguments
|
||||
|
||||
| Argument | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `type` | `(service | node )` | | A simple string to use for labelling |
|
||||
| `items` | `Array` | | An array of Consul healthchecks |
|
||||
|
||||
|
||||
## See
|
||||
|
||||
- [Template Source Code](./index.hbs)
|
||||
|
||||
---
|
|
@ -1,30 +1,49 @@
|
|||
{{#if (eq this.healthCheck.check 'empty') }}
|
||||
<dl class={{this.healthCheck.check}}>
|
||||
{{#let
|
||||
(group-by "Status" (or @items (array)))
|
||||
as |grouped|}}
|
||||
{{! Check the status of each Status of checks in order }}
|
||||
{{! First one to have more than one check wins }}
|
||||
{{! Otherwise we are empty }}
|
||||
{{#let
|
||||
(or
|
||||
(if (gt grouped.critical.length 0) grouped.critical)
|
||||
(if (gt grouped.warning.length 0) grouped.warning)
|
||||
(if (gt grouped.passing.length 0) grouped.passing)
|
||||
(array)
|
||||
)
|
||||
as |checks|}}
|
||||
{{#let
|
||||
checks.firstObject.Status
|
||||
as |status|}}
|
||||
<dl
|
||||
class={{class-map
|
||||
'consul-instance-checks'
|
||||
(array 'empty' (eq checks.length 0))
|
||||
(array status (not-eq checks.length 0))
|
||||
}}
|
||||
...attributes
|
||||
>
|
||||
<dt>
|
||||
<Tooltip>
|
||||
{{capitalize @type}} Checks
|
||||
</Tooltip>
|
||||
</dt>
|
||||
<dd>No {{@type}} checks</dd>
|
||||
{{#let
|
||||
(or
|
||||
(if (eq status 'critical') 'failing')
|
||||
(if (eq status 'warning') 'with a warning')
|
||||
status
|
||||
)
|
||||
as |humanized|}}
|
||||
<dd>
|
||||
{{or
|
||||
(if (eq checks.length 0) (concat 'No ' @type ' checks'))
|
||||
(if (eq checks.length @items.length) (concat 'All ' @type ' checks ' humanized))
|
||||
(concat checks.length '/' @items.length ' ' @type ' checks ' humanized)
|
||||
}}
|
||||
</dd>
|
||||
{{/let}}
|
||||
</dl>
|
||||
{{else}}
|
||||
{{#if (eq this.healthCheck.count @items.length)}}
|
||||
<dl class={{this.healthCheck.check}}>
|
||||
<dt>
|
||||
<Tooltip>
|
||||
{{capitalize @type}} Checks
|
||||
</Tooltip>
|
||||
</dt>
|
||||
<dd>All {{@type}} checks {{this.healthCheck.status}}</dd>
|
||||
</dl>
|
||||
{{else}}
|
||||
<dl class={{this.healthCheck.check}}>
|
||||
<dt>
|
||||
<Tooltip>
|
||||
{{capitalize @type}} Checks
|
||||
</Tooltip>
|
||||
</dt>
|
||||
<dd>{{this.healthCheck.count}}/{{@items.length}} {{@type}} checks {{this.healthCheck.status}}</dd>
|
||||
</dl>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{/let}}
|
||||
{{/let}}
|
||||
{{/let}}
|
|
@ -1,50 +0,0 @@
|
|||
import Component from '@glimmer/component';
|
||||
|
||||
export default class ConsulInstanceChecks extends Component {
|
||||
get healthCheck() {
|
||||
let ChecksCritical = 0;
|
||||
let ChecksWarning = 0;
|
||||
let ChecksPassing = 0;
|
||||
|
||||
this.args.items.forEach(item => {
|
||||
switch (item.Status) {
|
||||
case 'critical':
|
||||
ChecksCritical += 1;
|
||||
break;
|
||||
case 'warning':
|
||||
ChecksWarning += 1;
|
||||
break;
|
||||
case 'passing':
|
||||
ChecksPassing += 1;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
switch (true) {
|
||||
case ChecksCritical !== 0:
|
||||
return {
|
||||
check: 'critical',
|
||||
status: 'failing',
|
||||
count: ChecksCritical,
|
||||
};
|
||||
case ChecksWarning !== 0:
|
||||
return {
|
||||
check: 'warning',
|
||||
status: 'with warning',
|
||||
count: ChecksWarning,
|
||||
};
|
||||
case ChecksPassing !== 0:
|
||||
return {
|
||||
check: 'passing',
|
||||
status: 'passing',
|
||||
count: ChecksPassing,
|
||||
};
|
||||
default:
|
||||
return {
|
||||
check: 'empty',
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
.consul-instance-checks {
|
||||
& {
|
||||
@extend %horizontal-kv-list;
|
||||
}
|
||||
dt::before {
|
||||
@extend %as-pseudo;
|
||||
}
|
||||
&.passing dt::before {
|
||||
@extend %with-check-circle-fill-mask;
|
||||
color: rgb(var(--tone-green-500));
|
||||
}
|
||||
&.warning dt::before {
|
||||
@extend %with-alert-triangle-mask;
|
||||
color: rgb(var(--tone-orange-500));
|
||||
}
|
||||
&.critical dt::before {
|
||||
@extend %with-cancel-square-fill-mask;
|
||||
color: rgb(var(--tone-red-500));
|
||||
}
|
||||
&.empty dt::before {
|
||||
@extend %with-minus-square-fill-mask;
|
||||
color: rgb(var(--tone-gray-500));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
# Consul::ServiceInstance::List
|
||||
|
||||
A presentational component to show a list of Service Instances. The component
|
||||
will display slightly different information based on whether you want the list
|
||||
in a `@node` view or not.
|
||||
|
||||
Please note: A nice refactor here would be to let the node information be
|
||||
added from the outside via a slot. Once component is using the new row based
|
||||
scrollpane this can probably be looked at.
|
||||
|
||||
```hbs preview-template
|
||||
<figure>
|
||||
<figcaption>With no node given (i.e. from within a Service)</figcaption>
|
||||
<DataSource
|
||||
@src={{uri '/${partition}/${nspace}/${dc}/service-instances/for-service/${name}'
|
||||
(hash
|
||||
partition=''
|
||||
nspace=''
|
||||
dc='dc-1'
|
||||
name='service-name'
|
||||
)
|
||||
}}
|
||||
@onchange={{action (mut this.items) value="data"}}
|
||||
/>
|
||||
<Consul::ServiceInstance::List
|
||||
@routeName="dc.services.show"
|
||||
@items={{this.items}}
|
||||
@proxies={{array}}
|
||||
/>
|
||||
</figure>
|
||||
```
|
||||
|
||||
Component configured to show a list from within a node page.
|
||||
|
||||
```hbs preview-template
|
||||
<figure>
|
||||
<figcaption>With a node given (i.e. from within a Node)</figcaption>
|
||||
<DataSource
|
||||
@src={{uri '/${partition}/${nspace}/${dc}/node/${name}'
|
||||
(hash
|
||||
partition=''
|
||||
nspace=''
|
||||
dc='dc-1'
|
||||
name='node-0'
|
||||
)
|
||||
}}
|
||||
@onchange={{action (mut this.node) value="data"}}
|
||||
/>
|
||||
<DataSource
|
||||
@src={{uri '/${partition}/${nspace}/${dc}/service-instances/for-service/${name}'
|
||||
(hash
|
||||
partition=''
|
||||
nspace=''
|
||||
dc='dc-1'
|
||||
name='service-name'
|
||||
)
|
||||
}}
|
||||
@onchange={{action (mut this.items) value="data"}}
|
||||
/>
|
||||
<Consul::ServiceInstance::List
|
||||
@routeName="dc.services.show"
|
||||
@node={{this.node}}
|
||||
@items={{this.items}}
|
||||
@proxies={{array}}
|
||||
/>
|
||||
</figure>
|
||||
```
|
||||
|
||||
## Arguments
|
||||
|
||||
| Argument | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `items` | `Array` | | An array of Consul Service Instances |
|
||||
| `proxies` | `Array` | | An array of Consul Proxy Service Instances from the same Service |
|
||||
| `routeName` | `String` | | An Ember routeName for where clicking a row takes you |
|
||||
| `node` | `(Object | boolean)` | | Whether to show a node like view |
|
||||
|
||||
|
||||
## See
|
||||
|
||||
- [Template Source Code](./index.hbs)
|
||||
|
||||
---
|
|
@ -1,3 +1,8 @@
|
|||
{{#let
|
||||
|
||||
(to-hash @proxies "Service.Proxy.DestinationServiceID")
|
||||
|
||||
as |proxies|}}
|
||||
<ListCollection
|
||||
class="consul-service-instance-list"
|
||||
...attributes
|
||||
|
@ -15,15 +20,45 @@ as |item index|>
|
|||
{{/if}}
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="details">
|
||||
|
||||
{{#let
|
||||
(get proxies item.Service.ID)
|
||||
as |proxy|}}
|
||||
{{#let
|
||||
(merge-checks
|
||||
(array
|
||||
item.Checks
|
||||
(or
|
||||
proxy.Checks
|
||||
(array)
|
||||
)
|
||||
)
|
||||
)
|
||||
as |checks|}}
|
||||
|
||||
{{#if @node}}
|
||||
<Consul::ExternalSource @item={{item.Service}} />
|
||||
<Consul::InstanceChecks @type="service" @items={{item.Checks}} />
|
||||
<Consul::ExternalSource
|
||||
@item={{item.Service}}
|
||||
/>
|
||||
<Consul::InstanceChecks
|
||||
@type="service"
|
||||
@items={{filter-by 'ServiceID' '' checks}}
|
||||
/>
|
||||
{{else}}
|
||||
<Consul::ExternalSource @item={{item.Service}} />
|
||||
<Consul::InstanceChecks @type="service" @items={{filter-by 'ServiceID' '' item.Checks}} />
|
||||
<Consul::InstanceChecks @type="node" @items={{reject-by 'ServiceID' '' item.Checks}} />
|
||||
<Consul::ExternalSource
|
||||
@item={{item.Service}}
|
||||
/>
|
||||
|
||||
<Consul::InstanceChecks
|
||||
@type="service"
|
||||
@items={{filter-by 'ServiceID' '' checks}}
|
||||
/>
|
||||
<Consul::InstanceChecks
|
||||
@type="node"
|
||||
@items={{reject-by 'ServiceID' '' checks}}
|
||||
/>
|
||||
{{/if}}
|
||||
{{#if item.ProxyInstance}}
|
||||
{{#if proxy}}
|
||||
<dl class="mesh">
|
||||
<dt>
|
||||
<Tooltip>
|
||||
|
@ -73,5 +108,10 @@ as |item index|>
|
|||
</dl>
|
||||
{{/if}}
|
||||
<TagList @item={{item.Service}} />
|
||||
|
||||
{{/let}}
|
||||
{{/let}}
|
||||
|
||||
</BlockSlot>
|
||||
</ListCollection>
|
||||
</ListCollection>
|
||||
{{/let}}
|
|
@ -0,0 +1,12 @@
|
|||
import { helper } from '@ember/component/helper';
|
||||
import { get } from '@ember/object';
|
||||
|
||||
export default helper(([arrayLike = [], prop], hash) => {
|
||||
if (!Array.isArray(arrayLike)) {
|
||||
arrayLike = arrayLike.toArray();
|
||||
}
|
||||
return arrayLike.reduce((prev, item, i) => {
|
||||
prev[get(item, prop)] = item;
|
||||
return prev;
|
||||
}, {});
|
||||
});
|
|
@ -28,6 +28,8 @@ export default class Node extends Model {
|
|||
// MeshServiceInstances are all instances that aren't connect-proxies this
|
||||
// currently includes gateways as these need to show up in listings
|
||||
@filter('Services', item => item.Service.Kind !== 'connect-proxy') MeshServiceInstances;
|
||||
// ProxyServiceInstances are all instances that are connect-proxies
|
||||
@filter('Services', item => item.Service.Kind === 'connect-proxy') ProxyServiceInstances;
|
||||
|
||||
@computed('Checks.[]', 'ChecksCritical', 'ChecksPassing', 'ChecksWarning')
|
||||
get Status() {
|
||||
|
|
|
@ -80,6 +80,7 @@
|
|||
@import 'consul-ui/components/consul/upstream/list';
|
||||
@import 'consul-ui/components/consul/upstream-instance/list';
|
||||
@import 'consul-ui/components/consul/health-check/list';
|
||||
@import 'consul-ui/components/consul/instance-checks';
|
||||
@import 'consul-ui/components/consul/exposed-path/list';
|
||||
@import 'consul-ui/components/consul/external-source';
|
||||
@import 'consul-ui/components/consul/kind';
|
||||
|
|
|
@ -28,8 +28,9 @@ as |route|>
|
|||
)
|
||||
|
||||
route.model.item.MeshServiceInstances
|
||||
route.model.item.ProxyServiceInstances
|
||||
|
||||
as |sort filters items|}}
|
||||
as |sort filters items proxies|}}
|
||||
<div class="tab-section">
|
||||
{{#if (gt items.length 0) }}
|
||||
<input type="checkbox" id="toolbar-toggle" />
|
||||
|
@ -44,7 +45,6 @@ as |route|>
|
|||
@filter={{filters}}
|
||||
/>
|
||||
{{/if}}
|
||||
{{! filter out any sidecar proxies }}
|
||||
<DataCollection
|
||||
@type="service-instance"
|
||||
@sort={{sort.value}}
|
||||
|
@ -54,10 +54,10 @@ as |route|>
|
|||
as |collection|>
|
||||
<collection.Collection>
|
||||
<Consul::ServiceInstance::List
|
||||
@node={{item}}
|
||||
@node={{route.model.item}}
|
||||
@routeName="dc.services.show"
|
||||
@items={{collection.items}}
|
||||
@checks={{checks}}
|
||||
@proxies={{proxies}}
|
||||
/>
|
||||
</collection.Collection>
|
||||
<collection.Empty>
|
||||
|
|
|
@ -214,6 +214,7 @@ as |items item dc|}}
|
|||
@name={{routeName}}
|
||||
@model={{assign (hash
|
||||
items=items
|
||||
proxies=proxies
|
||||
item=item
|
||||
tabs=tabs
|
||||
) route.model}}
|
||||
|
|
|
@ -29,8 +29,9 @@ as |route|>
|
|||
)
|
||||
|
||||
route.model.items
|
||||
route.model.proxies.firstObject
|
||||
|
||||
as |sort filters items|}}
|
||||
as |sort filters items proxyMeta|}}
|
||||
{{#if (gt items.length 0) }}
|
||||
<input type="checkbox" id="toolbar-toggle" />
|
||||
<Consul::ServiceInstance::SearchBar
|
||||
|
@ -43,6 +44,19 @@ as |sort filters items|}}
|
|||
@filter={{filters}}
|
||||
/>
|
||||
{{/if}}
|
||||
{{#if proxyMeta.ServiceName}}
|
||||
<DataSource
|
||||
@src={{uri '/${partition}/${nspace}/${dc}/service-instances/for-service/${name}'
|
||||
(hash
|
||||
partition=route.params.partition
|
||||
nspace=route.params.nspace
|
||||
dc=route.params.dc
|
||||
name=proxyMeta.ServiceName
|
||||
)
|
||||
}}
|
||||
@onchange={{action (mut proxies) value="data"}}
|
||||
/>
|
||||
{{/if}}
|
||||
{{! Service > Service Instance view doesn't require filtering of proxies }}
|
||||
<DataCollection
|
||||
@type="service-instance"
|
||||
|
@ -55,6 +69,7 @@ as |sort filters items|}}
|
|||
<Consul::ServiceInstance::List
|
||||
@routeName="dc.services.instance"
|
||||
@items={{collection.items}}
|
||||
@proxies={{proxies}}
|
||||
/>
|
||||
</collection.Collection>
|
||||
<collection.Empty>
|
||||
|
|
|
@ -11,14 +11,14 @@ Feature: dc / services / instances / navigation
|
|||
And 3 instance models from yaml
|
||||
---
|
||||
- Service:
|
||||
Name: service-0
|
||||
Service: service-0
|
||||
ID: service-a
|
||||
Node:
|
||||
Node: node-0
|
||||
Checks:
|
||||
- Status: critical
|
||||
- Service:
|
||||
Name: service-0
|
||||
Service: service-0
|
||||
ID: service-b
|
||||
Node:
|
||||
Node: node-0
|
||||
|
@ -29,7 +29,7 @@ Feature: dc / services / instances / navigation
|
|||
# proxy on request.0 from yaml', 'And 1 proxy on request.1 from yaml' or
|
||||
# similar
|
||||
- Service:
|
||||
Name: service-0-proxy
|
||||
Service: service-0-proxy
|
||||
ID: service-a-proxy
|
||||
Node:
|
||||
Node: node-0
|
||||
|
|
Loading…
Reference in New Issue