ui: Redesign Node list page (#8567)
* Create ConsulNodeList component * Implement ConsulNodeList and the new Search/Sort to Node List page * Minor styling fix to align the first icons in composite row * Fix-up and add tests for the redesigned Node List page * Add Leader to composite row for Node List page * Add test for node leader
This commit is contained in:
parent
145bcdc2bb
commit
d8c14b51a3
|
@ -0,0 +1,41 @@
|
|||
{{#if (gt items.length 0)}}
|
||||
<ListCollection @items={{items}} class="consul-node-list" as |item index|>
|
||||
<BlockSlot @name="header">
|
||||
<dl class={{item.Status}}>
|
||||
<dt>
|
||||
Health
|
||||
</dt>
|
||||
<dd>
|
||||
<Tooltip @position="top-start">
|
||||
{{#if (eq 'critical' item.Status)}}
|
||||
At least one health check on this node is failing.
|
||||
{{else if (eq 'warning' item.Status)}}
|
||||
At least one health check on this node has a warning.
|
||||
{{else if (eq 'passing' item.Status)}}
|
||||
All health checks are passing.
|
||||
{{else}}
|
||||
There are no health checks.
|
||||
{{/if}}
|
||||
</Tooltip>
|
||||
</dd>
|
||||
</dl>
|
||||
<a data-test-node href={{href-to "dc.nodes.show" item.Node}}>
|
||||
{{item.Node}}
|
||||
</a>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="details">
|
||||
{{#if (eq item.Address leader.Address)}}
|
||||
<span class="leader" data-test-leader={{leader.Address}}>Leader</span>
|
||||
{{/if}}
|
||||
<dl>
|
||||
<dt>
|
||||
<CopyButton
|
||||
@value={{item.Address}}
|
||||
@name="Address"
|
||||
/>
|
||||
</dt>
|
||||
<dd>{{item.Address}}</dd>
|
||||
</dl>
|
||||
</BlockSlot>
|
||||
</ListCollection>
|
||||
{{/if}}
|
|
@ -0,0 +1,5 @@
|
|||
import Component from '@ember/component';
|
||||
|
||||
export default Component.extend({
|
||||
tagName: '',
|
||||
});
|
|
@ -2,26 +2,10 @@ import Controller from '@ember/controller';
|
|||
|
||||
export default Controller.extend({
|
||||
queryParams: {
|
||||
filterBy: {
|
||||
as: 'status',
|
||||
},
|
||||
sortBy: 'sort',
|
||||
search: {
|
||||
as: 'filter',
|
||||
replace: true,
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
hasStatus: function(status, checks) {
|
||||
if (status === '') {
|
||||
return true;
|
||||
}
|
||||
return checks.some(item => item.Status === status);
|
||||
},
|
||||
isHealthy: function(checks) {
|
||||
return !this.actions.isUnhealthy.apply(this, [checks]);
|
||||
},
|
||||
isUnhealthy: function(checks) {
|
||||
return checks.some(item => item.Status === 'critical' || item.Status === 'warning');
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -23,8 +23,7 @@ export function initialize(application) {
|
|||
policy: policy(filterable),
|
||||
role: role(filterable),
|
||||
kv: kv(filterable),
|
||||
healthyNode: node(filterable),
|
||||
unhealthyNode: node(filterable),
|
||||
node: node(filterable),
|
||||
serviceInstance: serviceNode(filterable),
|
||||
nodeservice: nodeService(filterable),
|
||||
service: service(filterable),
|
||||
|
|
|
@ -6,6 +6,7 @@ import token from 'consul-ui/sort/comparators/token';
|
|||
import role from 'consul-ui/sort/comparators/role';
|
||||
import policy from 'consul-ui/sort/comparators/policy';
|
||||
import nspace from 'consul-ui/sort/comparators/nspace';
|
||||
import node from 'consul-ui/sort/comparators/node';
|
||||
|
||||
export function initialize(container) {
|
||||
// Service-less injection using private properties at a per-project level
|
||||
|
@ -19,6 +20,7 @@ export function initialize(container) {
|
|||
role: role(),
|
||||
policy: policy(),
|
||||
nspace: nspace(),
|
||||
node: node(),
|
||||
};
|
||||
Sort.reopen({
|
||||
comparator: function(type) {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import Model from 'ember-data/model';
|
||||
import attr from 'ember-data/attr';
|
||||
import { computed } from '@ember/object';
|
||||
|
||||
export const PRIMARY_KEY = 'uid';
|
||||
export const SLUG_KEY = 'ID';
|
||||
|
@ -20,4 +21,25 @@ export default Model.extend({
|
|||
Coord: attr(),
|
||||
SyncTime: attr('number'),
|
||||
meta: attr(),
|
||||
Status: computed('Checks.[]', 'ChecksCritical', 'ChecksPassing', 'ChecksWarning', function() {
|
||||
switch (true) {
|
||||
case this.ChecksCritical !== 0:
|
||||
return 'critical';
|
||||
case this.ChecksWarning !== 0:
|
||||
return 'warning';
|
||||
case this.ChecksPassing !== 0:
|
||||
return 'passing';
|
||||
default:
|
||||
return 'empty';
|
||||
}
|
||||
}),
|
||||
ChecksCritical: computed('Checks.[]', function() {
|
||||
return this.Checks.filter(item => item.Status === 'critical').length;
|
||||
}),
|
||||
ChecksPassing: computed('Checks.[]', function() {
|
||||
return this.Checks.filter(item => item.Status === 'passing').length;
|
||||
}),
|
||||
ChecksWarning: computed('Checks.[]', function() {
|
||||
return this.Checks.filter(item => item.Status === 'warning').length;
|
||||
}),
|
||||
});
|
||||
|
|
|
@ -6,6 +6,7 @@ export default Route.extend({
|
|||
repo: service('repository/node'),
|
||||
data: service('data-source/service'),
|
||||
queryParams: {
|
||||
sortBy: 'sort',
|
||||
search: {
|
||||
as: 'filter',
|
||||
replace: true,
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
export default () => key => {
|
||||
if (key.startsWith('Status:')) {
|
||||
return function(serviceA, serviceB) {
|
||||
const [, dir] = key.split(':');
|
||||
let a, b;
|
||||
if (dir === 'asc') {
|
||||
b = serviceA;
|
||||
a = serviceB;
|
||||
} else {
|
||||
a = serviceA;
|
||||
b = serviceB;
|
||||
}
|
||||
switch (true) {
|
||||
case a.ChecksCritical > b.ChecksCritical:
|
||||
return 1;
|
||||
case a.ChecksCritical < b.ChecksCritical:
|
||||
return -1;
|
||||
default:
|
||||
switch (true) {
|
||||
case a.ChecksWarning > b.ChecksWarning:
|
||||
return 1;
|
||||
case a.ChecksWarning < b.ChecksWarning:
|
||||
return -1;
|
||||
default:
|
||||
switch (true) {
|
||||
case a.ChecksPassing < b.ChecksPassing:
|
||||
return 1;
|
||||
case a.ChecksPassing > b.ChecksPassing:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
}
|
||||
return key;
|
||||
};
|
|
@ -27,5 +27,9 @@
|
|||
width: 14px;
|
||||
height: 14px;
|
||||
margin-right: 2px;
|
||||
margin-top: 1px;
|
||||
position: relative;
|
||||
}
|
||||
%reduced-pill.leader::before {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
|
|
@ -34,3 +34,7 @@
|
|||
@extend %with-gateway-mask;
|
||||
background-color: $gray-500;
|
||||
}
|
||||
%reduced-pill.leader::before {
|
||||
@extend %with-star-outline-mask;
|
||||
background-color: $gray-500;
|
||||
}
|
||||
|
|
|
@ -156,7 +156,7 @@ $settings-svg: url('data:image/svg+xml;charset=UTF-8,<svg viewBox="0 0 24 24" fi
|
|||
$source-file-svg: url('data:image/svg+xml;charset=UTF-8,<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M13.714 9.143l3.572 3.571-3.572 3.572-.714-1.4 2.143-2.172L13 10.571l.714-1.428zm-3.571 1.4L8 12.714l2.143 2.143-.714 1.429-3.572-3.572L9.43 9.143l.714 1.4zm8.571 10.028H4.43V3.43h10l4.285 4.285v12.857zM15.143 2H4.429C3.643 2 3 2.643 3 3.429V20.57C3 21.357 3.643 22 4.429 22h14.285c.786 0 1.429-.643 1.429-1.429V7l-5-5z" fill="%23000"/></svg>');
|
||||
$sort-svg: url('data:image/svg+xml;charset=UTF-8,<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M0,10.0867585 L6,10.0867585 L6,8.40563206 L0,8.40563206 L0,10.0867585 L0,10.0867585 Z M3,12.4056321 L3,14.0867585 L0,14.0867585 L0,12.4056321 L3,12.4056321 Z M15.1301377,0 L15.1301377,1.68112641 L0,1.68112641 L0,0 L15.1301377,0 Z M13.8692929,4.62309763 L13.8692929,11.8384922 L16.8112641,8.89802258 L18,10.0867585 L13.0287297,15.0580288 L8.05745938,10.0867585 L9.24619526,8.89802258 L12.1881665,11.8393328 L12.1881665,4.62309763 L13.8692929,4.62309763 Z M10.0867585,4.20281603 L10.0867585,5.88394244 L0,5.88394244 L0,4.20281603 L10.0867585,4.20281603 Z" fill="%23000"/></svg>');
|
||||
$star-fill-svg: url('data:image/svg+xml;charset=UTF-8,<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21 12 17.27z" fill="%23000"/></svg>');
|
||||
$star-outline-svg: url('data:image/svg+xml;charset=UTF-8,<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M22 9.24l-7.19-.62L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21 12 17.27 18.18 21l-1.63-7.03L22 9.24zM12 15.4l-3.76 2.27 1-4.28-3.32-2.88 4.38-.38L12 6.1l1.71 4.04 4.38.38-3.32 2.88 1 4.28L12 15.4z" fill="%23000"/></svg>');
|
||||
$star-outline-svg: url('data:image/svg+xml;charset=UTF-8,<svg viewBox="0 0 26.5 26" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M1 13C1 6.37258 6.37258 1 13 1V1C19.6274 1 25 6.37258 25 13V13C25 19.6274 19.6274 25 13 25V25C6.37258 25 1 19.6274 1 13V13Z" fill="none"/><path fill-rule="evenodd" clip-rule="evenodd" d="M13 16.0742L16.605 18.25L15.6483 14.1492L18.8333 11.39L14.6392 11.0342L13 7.16667L11.3608 11.0342L7.16667 11.39L10.3517 14.1492L9.39501 18.25L13 16.0742Z" fill="%23C62A71"/><path d="M13 24C6.92487 24 2 19.0751 2 13H0C0 20.1797 5.8203 26 13 26V24ZM24 13C24 19.0751 19.0751 24 13 24V26C20.1797 26 26 20.1797 26 13H24ZM13 2C19.0751 2 24 6.92487 24 13H26C26 5.8203 20.1797 0 13 0V2ZM13 0C5.8203 0 0 5.8203 0 13H2C2 6.92487 6.92487 2 13 2V0Z" fill="%23E1E4E7"/></svg>');
|
||||
$star-svg: url('data:image/svg+xml;charset=UTF-8,<svg width="10" height="9" viewBox="0 0 10 9" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path id="a" d="M5 7.196L7.575 8.75l-.683-2.93 2.275-1.97-2.996-.254L5 .833 3.83 3.596.832 3.85l2.275 1.97-.683 2.93z"/></defs><use fill="%239E2159" xlink:href="%23a" fill-rule="evenodd"/></svg>');
|
||||
$sub-left-svg: url('data:image/svg+xml;charset=UTF-8,<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M11.42 9.42L7.83 13H18V4h2v11H7.83l3.59 3.58L10 20l-6-6 6-6 1.42 1.42z" fill="%23000"/></svg>');
|
||||
$sub-right-svg: url('data:image/svg+xml;charset=UTF-8,<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M14 20l-1.42-1.42L16.17 15H4V4h2v9h10.17l-3.59-3.58L14 8l6 6-6 6z" fill="%23000"/></svg>');
|
||||
|
|
|
@ -44,6 +44,7 @@
|
|||
}
|
||||
%composite-row-icon {
|
||||
margin-right: 6px;
|
||||
margin-left: -2px;
|
||||
}
|
||||
%composite-row-icon dt {
|
||||
display: none;
|
||||
|
|
|
@ -6,7 +6,8 @@ td strong {
|
|||
span.policy-service-identity,
|
||||
span.policy-node-identity,
|
||||
.consul-external-source,
|
||||
.consul-kind {
|
||||
.consul-kind,
|
||||
.leader {
|
||||
@extend %reduced-pill;
|
||||
}
|
||||
span.policy-service-identity::before,
|
||||
|
|
|
@ -1,15 +1,7 @@
|
|||
{{title 'Nodes'}}
|
||||
<EventSource @src={{items}} />
|
||||
{{#let (selectable-key-values
|
||||
(array "" "All (Any Status)")
|
||||
(array "critical" "Critical Checks")
|
||||
(array "warning" "Warning Checks")
|
||||
(array "passing" "Passing Checks")
|
||||
selected=filterBy
|
||||
)
|
||||
as |filter|
|
||||
}}
|
||||
<AppView @class="node list">
|
||||
{{#let (or sortBy "Node:asc") as |sort|}}
|
||||
<AppView @class="node list">
|
||||
<BlockSlot @name="header">
|
||||
<h1>
|
||||
Nodes <em>{{format-number items.length}} total</em>
|
||||
|
@ -17,121 +9,66 @@
|
|||
<label for="toolbar-toggle"></label>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="toolbar">
|
||||
{{#if (gt items.length 0) }}
|
||||
{{#if (gt items.length 0) }}
|
||||
<SearchBar
|
||||
data-test-catalog-filter
|
||||
@value={{search}}
|
||||
@onsearch={{action (mut search) value="target.value"}}
|
||||
@selected={{filter.selected}}
|
||||
@options={{filter.items}}
|
||||
@onchange={{action (mut filterBy) value='target.value'}}
|
||||
/>
|
||||
{{/if}}
|
||||
class="with-sort"
|
||||
>
|
||||
<BlockSlot @name="secondary">
|
||||
<PopoverSelect
|
||||
@position="right"
|
||||
@onchange={{action (mut sortBy) value='target.selected'}}
|
||||
@multiple={{false}}
|
||||
as |components|>
|
||||
<BlockSlot @name="selected">
|
||||
<span>
|
||||
{{#let (from-entries (array
|
||||
(array "Node:asc" "A to Z")
|
||||
(array "Node:desc" "Z to A")
|
||||
(array "Status:asc" "Unhealthy to Healthy")
|
||||
(array "Status:desc" "Healthy to Unhealthy")
|
||||
))
|
||||
as |selectable|
|
||||
}}
|
||||
{{get selectable sort}}
|
||||
{{/let}}
|
||||
</span>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="options">
|
||||
{{#let components.Optgroup components.Option as |Optgroup Option|}}
|
||||
<Optgroup @label="Name">
|
||||
<Option @value="Node:asc" @selected={{eq "Node:asc" sort}}>A to Z</Option>
|
||||
<Option @value="Node:desc" @selected={{eq "Node:desc" sort}}>Z to A</Option>
|
||||
</Optgroup>
|
||||
<Optgroup @label="Health Status">
|
||||
<Option @value="Status:asc" @selected={{eq "Status:asc" sort}}>Unhealthy to Healthy</Option>
|
||||
<Option @value="Status:desc" @selected={{eq "Status:desc" sort}}>Healthy to Unhealthy</Option>
|
||||
</Optgroup>
|
||||
{{/let}}
|
||||
</BlockSlot>
|
||||
</PopoverSelect>
|
||||
</BlockSlot>
|
||||
</SearchBar>
|
||||
{{/if}}
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="content">
|
||||
{{#let (filter-by "Checks" (action "isUnhealthy") items) as |unhealthy|}}
|
||||
{{#if (gt unhealthy.length 0) }}
|
||||
<div class="unhealthy">
|
||||
<h2>Unhealthy Nodes</h2>
|
||||
<div>
|
||||
{{! think about 2 differing views here }}
|
||||
<ul>
|
||||
<ChangeableSet
|
||||
@dispatcher={{
|
||||
searchable
|
||||
'unhealthyNode'
|
||||
(if (eq filter.selected.key "")
|
||||
unhealthy
|
||||
(filter-by "Checks" (action "hasStatus" filter.selected.key) unhealthy filter.selected.key)
|
||||
)
|
||||
}}
|
||||
@terms={{search}}
|
||||
>
|
||||
<BlockSlot @name="set" as |unhealthy|>
|
||||
{{#each unhealthy as |item|}}
|
||||
<HealthcheckedResource @tagName="li" @data-test-node={{item.Node}} @href={{href-to "dc.nodes.show" item.Node}} @name={{item.Node}} @address={{item.Address}} @checks={{item.Checks}}>
|
||||
<BlockSlot @name="icon">
|
||||
{{#if (eq item.Address leader.Address)}}
|
||||
<span data-test-leader={{leader.Address}} data-tooltip="Leader">Leader</span>
|
||||
{{/if}}
|
||||
</BlockSlot>
|
||||
</HealthcheckedResource>
|
||||
{{/each}}
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="empty">
|
||||
<li class="empty">
|
||||
<EmptyState>
|
||||
<BlockSlot @name="header">
|
||||
<h2>No nodes found</h2>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="body">
|
||||
<p>
|
||||
There don't seem to be any nodes matching that search.
|
||||
</p>
|
||||
</BlockSlot>
|
||||
</EmptyState>
|
||||
</li>
|
||||
</BlockSlot>
|
||||
</ChangeableSet>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/let}}
|
||||
{{#let (filter-by "Checks" (action "isHealthy") items) as |healthy|}}
|
||||
{{#if (gt healthy.length 0) }}
|
||||
<div class="healthy">
|
||||
<h2>Healthy Nodes</h2>
|
||||
<ChangeableSet
|
||||
@dispatcher={{
|
||||
searchable
|
||||
'healthyNode'
|
||||
(if (eq filter.selected.key "")
|
||||
healthy
|
||||
(filter-by "Checks" (action "hasStatus" filter.selected.key) healthy filter.selected.key)
|
||||
)
|
||||
}}
|
||||
@terms={{search}}
|
||||
>
|
||||
<BlockSlot @name="set" as |healthy|>
|
||||
<GridCollection @cellHeight={{92}} @items={{healthy}} as |item index|>
|
||||
<HealthcheckedResource @data-test-node={{item.Node}} @href={{href-to "dc.nodes.show" item.Node}} @name={{item.Node}} @address={{item.Address}} @checks={{item.Checks}}>
|
||||
<BlockSlot @name="icon">
|
||||
{{#if (eq item.Address leader.Address)}}
|
||||
<span data-test-leader={{leader.Address}} data-tooltip="Leader">Leader</span>
|
||||
{{/if}}
|
||||
</BlockSlot>
|
||||
</HealthcheckedResource>
|
||||
</GridCollection>
|
||||
{{#let (sort-by (comparator 'node' sort) items) as |sorted|}}
|
||||
<ChangeableSet @dispatcher={{searchable 'node' sorted}} @terms={{search}}>
|
||||
<BlockSlot @name="set" as |filtered|>
|
||||
<ConsulNodeList @items={{filtered}} @leader={{leader}} />
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="empty">
|
||||
<EmptyState>
|
||||
<BlockSlot @name="header">
|
||||
<h2>No nodes found</h2>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="body">
|
||||
<p>
|
||||
There don't seem to be any nodes matching that search.
|
||||
There don't seem to be any registered nodes, or you may not have access to view nodes yet.
|
||||
</p>
|
||||
</BlockSlot>
|
||||
</EmptyState>
|
||||
</BlockSlot>
|
||||
</ChangeableSet>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/let}}
|
||||
{{#if (eq items.length 0) }}
|
||||
<EmptyState @allowLogin={{true}}>
|
||||
<BlockSlot @name="header">
|
||||
<h2>Welcome to Nodes</h2>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="body">
|
||||
<p>
|
||||
There don't seem to be any nodes, or you may not have access to view nodes yet.
|
||||
</p>
|
||||
</BlockSlot>
|
||||
</EmptyState>
|
||||
{{/if}}
|
||||
</BlockSlot>
|
||||
</AppView>
|
||||
{{/let}}
|
||||
</BlockSlot>
|
||||
</AppView>
|
||||
{{/let}}
|
|
@ -3,68 +3,6 @@
|
|||
# to use the name filter UI also, then they can stay together
|
||||
@setupApplicationTest
|
||||
Feature: components / catalog-filter
|
||||
Scenario: Filtering [Model]
|
||||
Given 1 datacenter model with the value "dc-1"
|
||||
And 4 service models from yaml
|
||||
---
|
||||
- ChecksPassing: 1
|
||||
ChecksWarning: 0
|
||||
ChecksCritical: 0
|
||||
- ChecksPassing: 0
|
||||
ChecksWarning: 1
|
||||
ChecksCritical: 0
|
||||
- ChecksPassing: 0
|
||||
ChecksWarning: 0
|
||||
ChecksCritical: 1
|
||||
- ChecksPassing: 1
|
||||
ChecksWarning: 0
|
||||
ChecksCritical: 0
|
||||
---
|
||||
And 4 node models from yaml
|
||||
---
|
||||
- Checks:
|
||||
- Status: passing
|
||||
- Checks:
|
||||
- Status: warning
|
||||
- Checks:
|
||||
- Status: critical
|
||||
- Checks:
|
||||
- Status: passing
|
||||
---
|
||||
When I visit the [Page] page for yaml
|
||||
---
|
||||
dc: dc-1
|
||||
---
|
||||
Then the url should be [Url]
|
||||
|
||||
Then I see 4 [Model] models
|
||||
And I see allIsSelected on the filter
|
||||
|
||||
When I click passing on the filter
|
||||
And I see passingIsSelected on the filter
|
||||
And I see 2 [Model] models
|
||||
|
||||
When I click warning on the filter
|
||||
And I see warningIsSelected on the filter
|
||||
And I see 1 [Model] model
|
||||
|
||||
When I click critical on the filter
|
||||
And I see criticalIsSelected on the filter
|
||||
And I see 1 [Model] model
|
||||
|
||||
When I click all on the filter
|
||||
And I see allIsSelected on the filter
|
||||
Then I fill in with yaml
|
||||
---
|
||||
s: [Model]-0
|
||||
---
|
||||
And I see 1 [Model] model with the name "[Model]-0"
|
||||
|
||||
Where:
|
||||
-------------------------------------------------
|
||||
| Model | Page | Url |
|
||||
| node | nodes | /dc-1/nodes |
|
||||
-------------------------------------------------
|
||||
Scenario: Filtering [Model] in [Page]
|
||||
Given 1 datacenter model with the value "dc1"
|
||||
And 1 node model from yaml
|
||||
|
|
|
@ -20,14 +20,11 @@ Feature: dc / nodes / empty-ids: Hedge for if nodes come in over the API with no
|
|||
dc: dc-1
|
||||
---
|
||||
Then the url should be /dc-1/nodes
|
||||
Then I see name on the nodes like yaml
|
||||
Then I see name on the nodes vertically like yaml
|
||||
---
|
||||
- name-1
|
||||
- name-2
|
||||
- name-3
|
||||
- name-4
|
||||
- name-5
|
||||
|
||||
@ignore
|
||||
Scenario: Visually comparing
|
||||
Then the ".unhealthy" element should look like the "/node_modules/@hashicorp/consul-testing-extras/fixtures/dc/nodes/empty-ids.png" image
|
||||
---
|
|
@ -16,7 +16,7 @@ Feature: dc / nodes / index
|
|||
Then the url should be /dc-1/nodes
|
||||
And the title should be "Nodes - Consul"
|
||||
Then I see 3 node models
|
||||
Scenario: Seeing the leader in unhealthy listing
|
||||
Scenario: Seeing the leader in node listing
|
||||
Given 3 node models from yaml
|
||||
---
|
||||
- Address: 211.245.86.75
|
||||
|
@ -32,24 +32,7 @@ Feature: dc / nodes / index
|
|||
---
|
||||
Then the url should be /dc-1/nodes
|
||||
Then I see 3 node models
|
||||
And I see leader on the unHealthyNodes
|
||||
Scenario: Seeing the leader in healthy listing
|
||||
Given 3 node models from yaml
|
||||
---
|
||||
- Address: 211.245.86.75
|
||||
Checks:
|
||||
- Status: passing
|
||||
Name: Passing check
|
||||
- Address: 10.0.0.1
|
||||
- Address: 10.0.0.3
|
||||
---
|
||||
When I visit the nodes page for yaml
|
||||
---
|
||||
dc: dc-1
|
||||
---
|
||||
Then the url should be /dc-1/nodes
|
||||
Then I see 3 node models
|
||||
And I see leader on the healthyNodes
|
||||
And I see leader on the nodes.0
|
||||
Scenario: Searching the nodes with name and IP address
|
||||
Given 3 node models from yaml
|
||||
---
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
@setupApplicationTest
|
||||
Feature: dc / nodes / navigation
|
||||
Scenario: Clicking a node in the listing and back again
|
||||
Given 1 datacenter model with the value "dc-1"
|
||||
And 3 node models
|
||||
When I visit the nodes page for yaml
|
||||
---
|
||||
dc: dc-1
|
||||
---
|
||||
Then the url should be /dc-1/nodes
|
||||
And the title should be "Nodes - Consul"
|
||||
Then I see 3 node models
|
||||
When I click node on the nodes
|
||||
And I click "[data-test-back]"
|
||||
Then the url should be /dc-1/nodes
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
@setupApplicationTest
|
||||
Feature: dc / nodes / sorting
|
||||
Scenario:
|
||||
Given 1 datacenter model with the value "dc-1"
|
||||
And 6 node models from yaml
|
||||
---
|
||||
- Node: Node-A
|
||||
Checks:
|
||||
- Status: critical
|
||||
- Node: Node-B
|
||||
Checks:
|
||||
- Status: passing
|
||||
- Node: Node-C
|
||||
Checks:
|
||||
- Status: warning
|
||||
- Node: Node-D
|
||||
Checks:
|
||||
- Status: critical
|
||||
- Node: Node-E
|
||||
Checks:
|
||||
- Status: critical
|
||||
- Node: Node-F
|
||||
Checks:
|
||||
- Status: warning
|
||||
---
|
||||
When I visit the nodes page for yaml
|
||||
---
|
||||
dc: dc-1
|
||||
---
|
||||
When I click selected on the sort
|
||||
When I click options.3.button on the sort
|
||||
Then I see name on the nodes vertically like yaml
|
||||
---
|
||||
- Node-B
|
||||
- Node-C
|
||||
- Node-F
|
||||
- Node-A
|
||||
- Node-D
|
||||
- Node-E
|
||||
---
|
||||
When I click selected on the sort
|
||||
When I click options.2.button on the sort
|
||||
Then I see name on the nodes vertically like yaml
|
||||
---
|
||||
- Node-A
|
||||
- Node-D
|
||||
- Node-E
|
||||
- Node-C
|
||||
- Node-F
|
||||
- Node-B
|
||||
---
|
||||
When I click selected on the sort
|
||||
When I click options.0.button on the sort
|
||||
Then I see name on the nodes vertically like yaml
|
||||
---
|
||||
- Node-A
|
||||
- Node-B
|
||||
- Node-C
|
||||
- Node-D
|
||||
- Node-E
|
||||
- Node-F
|
||||
---
|
||||
When I click selected on the sort
|
||||
When I click options.1.button on the sort
|
||||
Then I see name on the nodes vertically like yaml
|
||||
---
|
||||
- Node-F
|
||||
- Node-E
|
||||
- Node-D
|
||||
- Node-C
|
||||
- Node-B
|
||||
- Node-A
|
||||
---
|
|
@ -43,7 +43,6 @@ Feature: page-navigation
|
|||
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
| Item | Model | URL | Endpoint | Back |
|
||||
| service | services | /dc-1/services/service-0/instances | /v1/discovery-chain/service-0?dc=dc-1&ns=@namespace | /dc-1/services |
|
||||
| node | nodes | /dc-1/nodes/node-0/health-checks | /v1/session/node/node-0?dc=dc-1&ns=@namespace | /dc-1/nodes |
|
||||
| kv | kvs | /dc-1/kv/0-key-value/edit | /v1/session/info/ee52203d-989f-4f7a-ab5a-2bef004164ca?dc=dc-1&ns=@namespace | /dc-1/kv |
|
||||
# | acl | acls | /dc-1/acls/anonymous | /v1/acl/info/anonymous?dc=dc-1 | /dc-1/acls |
|
||||
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
import steps from '../../steps';
|
||||
|
||||
// step definitions that are shared between features should be moved to the
|
||||
// tests/acceptance/steps/steps.js file
|
||||
|
||||
export default function(assert) {
|
||||
return steps(assert).then('I should find a file', function() {
|
||||
assert.ok(true, this.step);
|
||||
});
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
import steps from '../../steps';
|
||||
|
||||
// step definitions that are shared between features should be moved to the
|
||||
// tests/acceptance/steps/steps.js file
|
||||
|
||||
export default function(assert) {
|
||||
return steps(assert).then('I should find a file', function() {
|
||||
assert.ok(true, this.step);
|
||||
});
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
import steps from '../steps';
|
||||
|
||||
// step definitions that are shared between features should be moved to the
|
||||
// tests/acceptance/steps/steps.js file
|
||||
|
||||
export default function(assert) {
|
||||
return steps(assert).then('I should find a file', function() {
|
||||
assert.ok(true, this.step);
|
||||
});
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
import { module, skip } from 'qunit';
|
||||
import { setupRenderingTest } from 'ember-qunit';
|
||||
import { render } from '@ember/test-helpers';
|
||||
import { hbs } from 'ember-cli-htmlbars';
|
||||
|
||||
module('Integration | Component | consul-node-list', function(hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
skip('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`<ConsulNodeList />`);
|
||||
|
||||
assert.equal(this.element.textContent.trim(), '');
|
||||
|
||||
// Template block usage:
|
||||
await render(hbs`
|
||||
<ConsulNodeList>
|
||||
template block text
|
||||
</ConsulNodeList>
|
||||
`);
|
||||
|
||||
assert.equal(this.element.textContent.trim(), 'template block text');
|
||||
});
|
||||
});
|
|
@ -79,9 +79,6 @@ const tokenList = tokenListFactory(clickable, attribute, collection, deletable);
|
|||
const authForm = authFormFactory(submitable, clickable, attribute);
|
||||
const freetextFilter = freetextFilterFactory(triggerable);
|
||||
const catalogToolbar = searchBarFactory(freetextFilter);
|
||||
const catalogFilter = searchBarFactory(freetextFilter, () =>
|
||||
radiogroup('status', ['', 'passing', 'warning', 'critical'])
|
||||
);
|
||||
const aclFilter = searchBarFactory(freetextFilter, () =>
|
||||
radiogroup('type', ['', 'management', 'client'])
|
||||
);
|
||||
|
@ -153,7 +150,7 @@ export default {
|
|||
service(visitable, attribute, collection, text, consulIntentionList, catalogToolbar, tabgroup)
|
||||
),
|
||||
instance: create(instance(visitable, attribute, collection, text, tabgroup)),
|
||||
nodes: create(nodes(visitable, clickable, attribute, collection, catalogFilter)),
|
||||
nodes: create(nodes(visitable, text, clickable, attribute, collection, popoverSelect)),
|
||||
node: create(node(visitable, deletable, clickable, attribute, collection, tabgroup, text)),
|
||||
kvs: create(kvs(visitable, creatable, consulKvList)),
|
||||
kv: create(kv(visitable, attribute, submitable, deletable, cancelable, clickable)),
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
export default function(visitable, clickable, attribute, collection, filter) {
|
||||
export default function(visitable, text, clickable, attribute, collection, popoverSelect) {
|
||||
const node = {
|
||||
name: attribute('data-test-node'),
|
||||
name: text('[data-test-node]'),
|
||||
leader: attribute('data-test-leader', '[data-test-leader]'),
|
||||
node: clickable('header a'),
|
||||
node: clickable('a'),
|
||||
};
|
||||
return {
|
||||
visit: visitable('/:dc/nodes'),
|
||||
nodes: collection('[data-test-node]', node),
|
||||
healthyNodes: collection('.healthy [data-test-node]', node),
|
||||
unHealthyNodes: collection('.unhealthy [data-test-node]', node),
|
||||
filter: filter('[data-test-catalog-filter]'),
|
||||
nodes: collection('.consul-node-list [data-test-list-row]', node),
|
||||
home: clickable('[data-test-home]'),
|
||||
sort: popoverSelect(),
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue