ui: Redesign - Node service instances tab (#8204)
* Upgrade consul-api-dobule to version 3.1.3 * Create ConsulInstaceChecks component with test * Redesign: Service Instaces tab in for a Node * Update Node tests to work with the ConsulServiceInstancesList * Style fix to the copy button in the composite-row details * Delete helper and move logic to ConsulInstanceChecks component * Delete unused component consul-node-service-list
This commit is contained in:
parent
a4fe092e7a
commit
b0ecfc4109
|
@ -0,0 +1,32 @@
|
|||
{{#if (gt items.length 0)}}
|
||||
{{#if (eq healthCheck.check 'empty') }}
|
||||
<dl class={{healthCheck.check}}>
|
||||
<dt>
|
||||
<Tooltip>
|
||||
{{capitalize type}} Checks
|
||||
</Tooltip>
|
||||
</dt>
|
||||
<dd>No {{type}} checks</dd>
|
||||
</dl>
|
||||
{{else}}
|
||||
{{#if (eq healthCheck.count items.length)}}
|
||||
<dl class={{healthCheck.check}}>
|
||||
<dt>
|
||||
<Tooltip>
|
||||
{{capitalize type}} Checks
|
||||
</Tooltip>
|
||||
</dt>
|
||||
<dd>All {{type}} checks {{healthCheck.status}}</dd>
|
||||
</dl>
|
||||
{{else}}
|
||||
<dl class={{healthCheck.check}}>
|
||||
<dt>
|
||||
<Tooltip>
|
||||
{{capitalize type}} Checks
|
||||
</Tooltip>
|
||||
</dt>
|
||||
<dd>{{healthCheck.count}}/{{items.length}} {{type}} checks {{healthCheck.status}}</dd>
|
||||
</dl>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{/if}}
|
|
@ -0,0 +1,52 @@
|
|||
import Component from '@ember/component';
|
||||
import { computed } from '@ember/object';
|
||||
|
||||
export default Component.extend({
|
||||
tagName: '',
|
||||
healthCheck: computed('items.[]', function() {
|
||||
let ChecksCritical = 0;
|
||||
let ChecksWarning = 0;
|
||||
let ChecksPassing = 0;
|
||||
|
||||
this.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',
|
||||
};
|
||||
}
|
||||
}),
|
||||
});
|
|
@ -1,80 +1,25 @@
|
|||
{{#if (gt items.length 0)}}
|
||||
<ListCollection @items={{items}} class="consul-service-instance-list" as |item index|>
|
||||
<BlockSlot @name="header">
|
||||
<a href={{href-to routeName item.Service.Service item.Node.Node (or item.Service.ID item.Service.Service)}}>
|
||||
{{#if (eq routeName "dc.services.show")}}
|
||||
<a data-test-service-name href={{href-to routeName item.Service}}>
|
||||
{{item.ID}}
|
||||
</a>
|
||||
{{else}}
|
||||
<a data-test-service-name href={{href-to routeName item.Service.Service item.Node.Node (or item.Service.ID item.Service.Service)}}>
|
||||
{{item.Service.ID}}
|
||||
</a>
|
||||
{{/if}}
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="details">
|
||||
{{#if checks}}
|
||||
<ConsulExternalSource @item={{item}} />
|
||||
<ConsulInstanceChecks @type="service" @items={{get checks item.Service}} />
|
||||
{{else}}
|
||||
<ConsulExternalSource @item={{item.Service}} />
|
||||
{{#let (reject-by 'ServiceID' '' item.Checks) as |checks|}}
|
||||
{{#let (service/instance-checks checks) as |serviceCheck| }}
|
||||
{{#if (eq serviceCheck.check 'empty') }}
|
||||
<dl class={{serviceCheck.check}}>
|
||||
<dt>
|
||||
<Tooltip>
|
||||
Service Checks
|
||||
</Tooltip>
|
||||
</dt>
|
||||
<dd>No service checks</dd>
|
||||
</dl>
|
||||
{{else}}
|
||||
{{#if (eq serviceCheck.count checks.length)}}
|
||||
<dl class={{serviceCheck.check}}>
|
||||
<dt>
|
||||
<Tooltip>
|
||||
Service Checks
|
||||
</Tooltip>
|
||||
</dt>
|
||||
<dd>All service checks {{serviceCheck.status}}</dd>
|
||||
</dl>
|
||||
{{else}}
|
||||
<dl class={{serviceCheck.check}}>
|
||||
<dt>
|
||||
<Tooltip>
|
||||
Service Checks
|
||||
</Tooltip>
|
||||
</dt>
|
||||
<dd>{{serviceCheck.count}}/{{checks.length}} service checks {{serviceCheck.status}}</dd>
|
||||
</dl>
|
||||
<ConsulInstanceChecks @type="service" @items={{reject-by 'ServiceID' '' item.Checks}} />
|
||||
<ConsulInstanceChecks @type="node" @items={{filter-by 'ServiceID' '' item.Checks}} />
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{/let}}
|
||||
{{/let}}
|
||||
{{#let (filter-by 'ServiceID' '' item.Checks) as |checks|}}
|
||||
{{#let (service/instance-checks checks) as |nodeCheck| }}
|
||||
{{#if (eq nodeCheck.check 'empty') }}
|
||||
<dl class={{nodeCheck.check}}>
|
||||
<dt>
|
||||
<Tooltip>
|
||||
Node Checks
|
||||
</Tooltip>
|
||||
</dt>
|
||||
<dd>No node checks</dd>
|
||||
</dl>
|
||||
{{else}}
|
||||
{{#if (eq nodeCheck.count checks.length)}}
|
||||
<dl class={{nodeCheck.check}}>
|
||||
<dt>
|
||||
<Tooltip>
|
||||
Node Checks
|
||||
</Tooltip>
|
||||
</dt>
|
||||
<dd>All node checks {{nodeCheck.status}}</dd>
|
||||
</dl>
|
||||
{{else}}
|
||||
<dl class={{nodeCheck.check}}>
|
||||
<dt>
|
||||
<Tooltip>
|
||||
Node Checks
|
||||
</Tooltip>
|
||||
</dt>
|
||||
<dd>{{nodeCheck.count}}/{{checks.length}} node checks {{nodeCheck.status}}</dd>
|
||||
</dl>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{/let}}
|
||||
{{/let}}
|
||||
{{#if (get proxies item.Service.ID)}}
|
||||
<dl class="proxy">
|
||||
<dt>
|
||||
|
@ -99,6 +44,7 @@
|
|||
</dd>
|
||||
</dl>
|
||||
{{/if}}
|
||||
{{#if item.Service.Port}}
|
||||
<dl class="address" data-test-address>
|
||||
<dt>
|
||||
<Tooltip>
|
||||
|
@ -113,7 +59,23 @@
|
|||
{{/if}}
|
||||
</dd>
|
||||
</dl>
|
||||
{{/if}}
|
||||
{{#if (and checks item.Port)}}
|
||||
<dl>
|
||||
<dt>
|
||||
<CopyButton
|
||||
@value={{item.Port}}
|
||||
@name="Port"
|
||||
/>
|
||||
</dt>
|
||||
<dd data-test-service-port={{item.Port}}>:{{item.Port}}</dd>
|
||||
</dl>
|
||||
{{/if}}
|
||||
{{#if checks}}
|
||||
<TagList @item={{item}} />
|
||||
{{else}}
|
||||
<TagList @item={{item.Service}} />
|
||||
{{/if}}
|
||||
</BlockSlot>
|
||||
</ListCollection>
|
||||
{{/if}}
|
|
@ -1,5 +1,6 @@
|
|||
import Controller from '@ember/controller';
|
||||
import { alias } from '@ember/object/computed';
|
||||
import { get, computed } from '@ember/object';
|
||||
|
||||
export default Controller.extend({
|
||||
items: alias('item.Services'),
|
||||
|
@ -9,4 +10,19 @@ export default Controller.extend({
|
|||
replace: true,
|
||||
},
|
||||
},
|
||||
checks: computed('item.Checks.[]', function() {
|
||||
const checks = {};
|
||||
get(this, 'item.Checks')
|
||||
.filter(item => {
|
||||
return item.ServiceID !== '';
|
||||
})
|
||||
.forEach(item => {
|
||||
if (typeof checks[item.ServiceID] === 'undefined') {
|
||||
checks[item.ServiceID] = [];
|
||||
}
|
||||
checks[item.ServiceID].push(item);
|
||||
});
|
||||
|
||||
return checks;
|
||||
}),
|
||||
});
|
||||
|
|
|
@ -1,50 +0,0 @@
|
|||
import { helper } from '@ember/component/helper';
|
||||
|
||||
export function healthChecks([items], hash) {
|
||||
let ChecksCritical = 0;
|
||||
let ChecksWarning = 0;
|
||||
let ChecksPassing = 0;
|
||||
|
||||
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',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default helper(healthChecks);
|
|
@ -67,7 +67,7 @@ export const routes = {
|
|||
_options: { path: '/health-checks' },
|
||||
},
|
||||
services: {
|
||||
_options: { path: '/services' },
|
||||
_options: { path: '/service-instances' },
|
||||
},
|
||||
rtt: {
|
||||
_options: { path: '/round-trip-time' },
|
||||
|
|
|
@ -73,6 +73,7 @@
|
|||
}
|
||||
%composite-row-detail .copy-button {
|
||||
margin-right: 4px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
%composite-row-header .copy-button {
|
||||
margin-left: 4px;
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
compact
|
||||
(array
|
||||
(hash label="Health Checks" href=(href-to "dc.nodes.show.healthchecks") selected=(is-href "dc.nodes.show.healthchecks"))
|
||||
(hash label="Services" href=(href-to "dc.nodes.show.services") selected=(is-href "dc.nodes.show.services"))
|
||||
(hash label="Service Instances" href=(href-to "dc.nodes.show.services") selected=(is-href "dc.nodes.show.services"))
|
||||
(if tomography.distances (hash label="Round Trip Time" href=(href-to "dc.nodes.show.rtt") selected=(is-href "dc.nodes.show.rtt")) '')
|
||||
(hash label="Lock Sessions" href=(href-to "dc.nodes.show.sessions") selected=(is-href "dc.nodes.show.sessions"))
|
||||
(hash label="Metadata" href=(href-to "dc.nodes.show.metadata") selected=(is-href "dc.nodes.show.metadata"))
|
||||
|
|
|
@ -10,36 +10,7 @@
|
|||
{{/if}}
|
||||
<ChangeableSet @dispatcher={{searchable 'nodeservice' items}} @terms={{search}}>
|
||||
<BlockSlot @name="set" as |filtered|>
|
||||
<TabularCollection
|
||||
data-test-services
|
||||
@items={{filtered}} as |item index|
|
||||
>
|
||||
<BlockSlot @name="header">
|
||||
<th>Service</th>
|
||||
<th>Port</th>
|
||||
<th>Tags</th>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="row">
|
||||
<td data-test-service-name={{item.Service}}>
|
||||
<a href={{href-to 'dc.services.show' item.Service}}>
|
||||
{{#let (service/external-source item) as |externalSource| }}
|
||||
{{#if externalSource }}
|
||||
<span data-test-external-source={{externalSource}} style={{concat 'background-image: var(--' externalSource '-icon)'}}></span>
|
||||
{{else}}
|
||||
<span></span>
|
||||
{{/if}}
|
||||
{{/let}}
|
||||
{{item.Service}}{{#if (not-eq item.ID item.Service) }} <em data-test-service-id="{{item.ID}}">({{item.ID}})</em>{{/if}}
|
||||
</a>
|
||||
</td>
|
||||
<td data-test-service-port={{item.Port}} class="port">
|
||||
{{item.Port}}
|
||||
</td>
|
||||
<td data-test-service-tags>
|
||||
<TagList @item={{item}} />
|
||||
</td>
|
||||
</BlockSlot>
|
||||
</TabularCollection>
|
||||
<ConsulServiceInstanceList @routeName="dc.services.show" @items={{filtered}} @checks={{checks}}/>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="empty">
|
||||
<p>
|
||||
|
|
|
@ -90,8 +90,8 @@ Feature: components / catalog-filter
|
|||
node: node-0
|
||||
---
|
||||
# And I see 3 healthcheck model with the name "Disk Util"
|
||||
When I click services on the tabs
|
||||
And I see servicesIsSelected on the tabs
|
||||
When I click serviceInstances on the tabs
|
||||
And I see serviceInstancesIsSelected on the tabs
|
||||
|
||||
Then I fill in with yaml
|
||||
---
|
||||
|
@ -101,12 +101,6 @@ Feature: components / catalog-filter
|
|||
And I see 1 [Model] model with the port "65535"
|
||||
Then I fill in with yaml
|
||||
---
|
||||
s: service-0-with-id
|
||||
---
|
||||
And I see 1 [Model] model
|
||||
And I see 1 [Model] model with the id "service-0-with-id"
|
||||
Then I fill in with yaml
|
||||
---
|
||||
s: hard drive
|
||||
---
|
||||
And I see 1 [Model] model with the name "[Model]-1"
|
||||
|
|
|
@ -30,24 +30,18 @@ Feature: dc / nodes / services / list: Node > Services Listing
|
|||
Tags: []
|
||||
Meta:
|
||||
external-source: kubernetes
|
||||
- ID: 'service-4'
|
||||
Port: 3
|
||||
Service: 'service-4'
|
||||
Tags: []
|
||||
Meta: ~
|
||||
---
|
||||
When I visit the node page for yaml
|
||||
---
|
||||
dc: dc1
|
||||
node: node-0
|
||||
---
|
||||
When I click services on the tabs
|
||||
And I see servicesIsSelected on the tabs
|
||||
When I click serviceInstances on the tabs
|
||||
And I see serviceInstancesIsSelected on the tabs
|
||||
And I see externalSource on the services like yaml
|
||||
---
|
||||
- consul
|
||||
- nomad
|
||||
- terraform
|
||||
- kubernetes
|
||||
- ~
|
||||
---
|
||||
|
|
|
@ -11,8 +11,8 @@ Feature: dc / nodes / show: Show node
|
|||
---
|
||||
And I see healthChecksIsSelected on the tabs
|
||||
|
||||
When I click services on the tabs
|
||||
And I see servicesIsSelected on the tabs
|
||||
When I click serviceInstances on the tabs
|
||||
And I see serviceInstancesIsSelected on the tabs
|
||||
|
||||
When I click roundTripTime on the tabs
|
||||
And I see roundTripTimeIsSelected on the tabs
|
||||
|
@ -34,14 +34,14 @@ Feature: dc / nodes / show: Show node
|
|||
---
|
||||
And I see healthChecksIsSelected on the tabs
|
||||
|
||||
When I click services on the tabs
|
||||
And I see servicesIsSelected on the tabs
|
||||
When I click serviceInstances on the tabs
|
||||
And I see serviceInstancesIsSelected on the tabs
|
||||
|
||||
And I don't see roundTripTime on the tabs
|
||||
|
||||
When I click lockSessions on the tabs
|
||||
And I see lockSessionsIsSelected on the tabs
|
||||
Scenario: Given 1 node with no checks all the tabs are visible but the Services tab is selected
|
||||
Scenario: Given 1 node with no checks all the tabs are visible but the serviceInstances tab is selected
|
||||
Given 1 node models from yaml
|
||||
---
|
||||
ID: node-0
|
||||
|
@ -53,10 +53,10 @@ Feature: dc / nodes / show: Show node
|
|||
node: node-0
|
||||
---
|
||||
And I see healthChecks on the tabs
|
||||
And I see services on the tabs
|
||||
And I see serviceInstances on the tabs
|
||||
And I don't see roundTripTime on the tabs
|
||||
And I see lockSessions on the tabs
|
||||
And I see servicesIsSelected on the tabs
|
||||
And I see serviceInstancesIsSelected on the tabs
|
||||
Scenario: A node warns when deregistered whilst blocking
|
||||
Given 1 node model from yaml
|
||||
---
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
import { module, test } from 'qunit';
|
||||
import { setupRenderingTest } from 'ember-qunit';
|
||||
import { render } from '@ember/test-helpers';
|
||||
import { hbs } from 'ember-cli-htmlbars';
|
||||
|
||||
module('Integration | Component | consul-instance-checks', function(hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
test('it renders', async function(assert) {
|
||||
// Set any properties with this.set('myProperty', 'value');
|
||||
// Handle any actions with this.set('myAction', function(val) { ... });
|
||||
|
||||
await render(hbs`<ConsulInstanceChecks />`);
|
||||
|
||||
assert.equal(this.element.textContent.trim(), '');
|
||||
|
||||
// Template block usage:
|
||||
await render(hbs`
|
||||
<ConsulInstanceChecks>
|
||||
</ConsulInstanceChecks>
|
||||
`);
|
||||
|
||||
assert.equal(this.element.textContent.trim(), '');
|
||||
});
|
||||
});
|
|
@ -138,7 +138,7 @@ export default {
|
|||
),
|
||||
instance: create(instance(visitable, attribute, collection, text, tabgroup)),
|
||||
nodes: create(nodes(visitable, clickable, attribute, collection, catalogFilter)),
|
||||
node: create(node(visitable, deletable, clickable, attribute, collection, tabgroup)),
|
||||
node: create(node(visitable, deletable, clickable, attribute, collection, tabgroup, text)),
|
||||
kvs: create(kvs(visitable, deletable, creatable, clickable, attribute, collection)),
|
||||
kv: create(kv(visitable, attribute, submitable, deletable, cancelable, clickable)),
|
||||
acls: create(acls(visitable, deletable, creatable, clickable, attribute, collection, aclFilter)),
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
export default function(visitable, deletable, clickable, attribute, collection, tabs) {
|
||||
export default function(visitable, deletable, clickable, attribute, collection, tabs, text) {
|
||||
return {
|
||||
visit: visitable('/:dc/nodes/:node'),
|
||||
tabs: tabs('tab', [
|
||||
'health-checks',
|
||||
'services',
|
||||
'service-instances',
|
||||
'round-trip-time',
|
||||
'lock-sessions',
|
||||
'metadata',
|
||||
|
@ -11,11 +11,10 @@ export default function(visitable, deletable, clickable, attribute, collection,
|
|||
healthchecks: collection('[data-test-node-healthcheck]', {
|
||||
name: attribute('data-test-node-healthcheck'),
|
||||
}),
|
||||
services: collection('#services [data-test-tabular-row]', {
|
||||
id: attribute('data-test-service-id', '[data-test-service-id]'),
|
||||
name: attribute('data-test-service-name', '[data-test-service-name]'),
|
||||
port: attribute('data-test-service-port', '.port'),
|
||||
externalSource: attribute('data-test-external-source', 'a span'),
|
||||
services: collection('.consul-service-instance-list > ul > li:not(:first-child)', {
|
||||
name: text('[data-test-service-name]'),
|
||||
port: attribute('data-test-service-port', '[data-test-service-port]'),
|
||||
externalSource: attribute('data-test-external-source', '[data-test-external-source]'),
|
||||
}),
|
||||
sessions: collection(
|
||||
'#lock-sessions [data-test-tabular-row]',
|
||||
|
|
|
@ -1211,9 +1211,9 @@
|
|||
js-yaml "^3.13.1"
|
||||
|
||||
"@hashicorp/consul-api-double@^3.0.0":
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@hashicorp/consul-api-double/-/consul-api-double-3.1.2.tgz#3c3b929ab0f8aff5f503728337caf1c1a41171fb"
|
||||
integrity sha512-igs6f9fiA+z2Us1oLZ49/sEU0WsL+s7a1pnwFtED2xdI8tn5hz9G0doYfOxmi04IifNxv80NVifl3rZl2rn2tw==
|
||||
version "3.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@hashicorp/consul-api-double/-/consul-api-double-3.1.3.tgz#62f8780c8513e9b37f29302543c29143b4024141"
|
||||
integrity sha512-IZ90RK8g4/QPxQpRLnatwpBQh9Z3kQJjOGiUVz+CrSlXg4KRLhQCFFz/gI2vmhAXRACyTxIWuydPV6BcN4ptZA==
|
||||
|
||||
"@hashicorp/ember-cli-api-double@^3.1.0":
|
||||
version "3.1.0"
|
||||
|
|
Loading…
Reference in New Issue