ui: Redesign Service List page (#7605)

* Create GridCollection for nodes page with styling

* Update ListCollection styling

* Update TagList styling

* Create CompositeRow styling component

* Update ConsulServiceList component with styling

* Create service health-checks helper

* Add InstanceCount to the service model

* Add tag-svg to codebase

* Create and update tests for service-list page

* Upgrade @hashicorp/consul-api-double to 2.14.0
This commit is contained in:
Kenia 2020-04-08 13:09:36 -04:00 committed by John Cowen
parent e34c16a90c
commit d0c4312923
40 changed files with 482 additions and 208 deletions

View File

@ -1,34 +1,15 @@
{{yield}}
{{#if (gt items.length 0)}}
<TabularCollection @items={{items}} as |item index|>
<BlockSlot @name="header">
<th style={{remainingWidth}}>Service</th>
<th style={{totalWidth}}>
Health Checks
<span>
<em role="tooltip">The number of health checks for the service on all nodes</em>
</span>
</th>
<th style={{remainingWidth}}>Tags</th>
</BlockSlot>
<BlockSlot @name="row">
<td data-test-service={{item.Name}} style={{remainingWidth}}>
<ListCollection @cellHeight={{73}} @items={{items}} class="consul-service-list" as |item index|>
<a href={{href-to routeName item.Name}}>
{{#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}}
<span class={{service/health-checks item}}></span>
<span>
{{item.Name}}
</span>
<span data-test-external-source="{{service/external-source item}}" class={{service/external-source item}}></span>
<YieldSlot @name="metadata" @params={{block-params item}}>
{{yield}}
</YieldSlot>
</a>
</td>
<td style={{totalWidth}}>
<HealthcheckInfo @passing={{item.ChecksPassing}} @warning={{item.ChecksWarning}} @critical={{item.ChecksCritical}} @passingWidth={{passingWidth}} @warningWidth={{warningWidth}} @criticalWidth={{criticalWidth}} />
</td>
<td style={{remainingWidth}}>
<TagList @items={{item.Tags}} />
</td>
</BlockSlot>
</TabularCollection>
</ListCollection>
{{/if}}

View File

@ -1,65 +1,6 @@
import Component from '@ember/component';
import { get, computed } from '@ember/object';
import { htmlSafe } from '@ember/string';
import Slotted from 'block-slots';
const max = function(arr, prop) {
return arr.reduce(function(prev, item) {
return Math.max(prev, get(item, prop));
}, 0);
};
const chunk = function(str, size) {
const num = Math.ceil(str.length / size);
const chunks = new Array(num);
for (let i = 0, o = 0; i < num; ++i, o += size) {
chunks[i] = str.substr(o, size);
}
return chunks;
};
const width = function(num) {
const str = num.toString();
const len = str.length;
const commas = chunk(str, 3).length - 1;
return commas * 4 + len * 10;
};
const widthDeclaration = function(num) {
return htmlSafe(`width: ${num}px`);
};
export default Component.extend({
export default Component.extend(Slotted, {
tagName: '',
onchange: function() {},
maxWidth: computed('{maxPassing,maxWarning,maxCritical}', function() {
const PADDING = 32 * 3 + 13;
return ['maxPassing', 'maxWarning', 'maxCritical'].reduce((prev, item) => {
return prev + width(get(this, item));
}, PADDING);
}),
totalWidth: computed('maxWidth', function() {
return widthDeclaration(get(this, 'maxWidth'));
}),
remainingWidth: computed('maxWidth', function() {
// maxWidth is the maximum width of the healthchecks column
// there are currently 2 other columns so divide it by 2 and
// take that off 50% (100% / number of fluid columns)
// also we added a Type column which we've currently fixed to 100px
// so again divide that by 2 and take it off each fluid column
return htmlSafe(`width: calc(50% - 50px - ${Math.round(get(this, 'maxWidth') / 2)}px)`);
}),
maxPassing: computed('items.[]', function() {
return max(get(this, 'items'), 'ChecksPassing');
}),
maxWarning: computed('items.[]', function() {
return max(get(this, 'items'), 'ChecksWarning');
}),
maxCritical: computed('items.[]', function() {
return max(get(this, 'items'), 'ChecksCritical');
}),
passingWidth: computed('maxPassing', function() {
return widthDeclaration(width(get(this, 'maxPassing')));
}),
warningWidth: computed('maxWarning', function() {
return widthDeclaration(width(get(this, 'maxWarning')));
}),
criticalWidth: computed('maxCritical', function() {
return widthDeclaration(width(get(this, 'maxCritical')));
}),
});

View File

@ -0,0 +1,6 @@
<EmberNativeScrollable @tagName="ul" @content-size={{_contentSize}} @scroll-left={{_scrollLeft}} @scroll-top={{_scrollTop}} @scrollChange={{action "scrollChange"}} @clientSizeChange={{action "clientSizeChange"}}>
<li></li>
{{~#each _cells as |cell|~}}
<li style={{{cell.style}}}>{{yield cell.item cell.index }}</li>
{{~/each~}}
</EmberNativeScrollable>

View File

@ -0,0 +1,78 @@
import { inject as service } from '@ember/service';
import { computed, get, set } from '@ember/object';
import Component from 'ember-collection/components/ember-collection';
import PercentageColumns from 'ember-collection/layouts/percentage-columns';
import style from 'ember-computed-style';
import WithResizing from 'consul-ui/mixins/with-resizing';
export default Component.extend(WithResizing, {
dom: service('dom'),
tagName: 'div',
attributeBindings: ['style'],
height: 500,
cellHeight: 113,
style: style('getStyle'),
classNames: ['grid-collection'],
init: function() {
this._super(...arguments);
this.columns = [25, 25, 25, 25];
},
didReceiveAttrs: function() {
this._super(...arguments);
this._cellLayout = this['cell-layout'] = new PercentageColumns(
get(this, 'items.length'),
get(this, 'columns'),
get(this, 'cellHeight')
);
},
getStyle: computed('height', function() {
return {
height: get(this, 'height'),
};
}),
resize: function(e) {
// TODO: This top part is very similar to resize in tabular-collection
// see if it make sense to DRY out
const dom = get(this, 'dom');
const $appContent = dom.element('main > div');
if ($appContent) {
const rect = this.element.getBoundingClientRect();
const $footer = dom.element('footer[role="contentinfo"]');
const space = rect.top + $footer.clientHeight;
const height = e.detail.height - space;
this.set('height', Math.max(0, height));
this.updateItems();
this.updateScrollPosition();
}
const width = e.detail.width;
const len = get(this, 'columns.length');
switch (true) {
case width > 1013:
if (len != 4) {
set(this, 'columns', [25, 25, 25, 25]);
}
break;
case width > 744:
if (len != 3) {
set(this, 'columns', [33, 33, 34]);
}
break;
case width > 487:
if (len != 2) {
set(this, 'columns', [50, 50]);
}
break;
case width < 488:
if (len != 1) {
set(this, 'columns', [100]);
}
}
if (len !== get(this, 'columns.length')) {
this._cellLayout = this['cell-layout'] = new PercentageColumns(
get(this, 'items.length'),
get(this, 'columns'),
get(this, 'cellHeight')
);
}
},
});

View File

@ -1,5 +1,5 @@
import { inject as service } from '@ember/service';
import { computed, get, set } from '@ember/object';
import { computed, get } from '@ember/object';
import Component from 'ember-collection/components/ember-collection';
import PercentageColumns from 'ember-collection/layouts/percentage-columns';
import style from 'ember-computed-style';
@ -10,12 +10,11 @@ export default Component.extend(WithResizing, {
tagName: 'div',
attributeBindings: ['style'],
height: 500,
cellHeight: 113,
style: style('getStyle'),
classNames: ['list-collection'],
init: function() {
this._super(...arguments);
this.columns = [25, 25, 25, 25];
this.columns = [100];
},
didReceiveAttrs: function() {
this._super(...arguments);
@ -36,43 +35,14 @@ export default Component.extend(WithResizing, {
const dom = get(this, 'dom');
const $appContent = dom.element('main > div');
if ($appContent) {
const border = 1;
const rect = this.element.getBoundingClientRect();
const $footer = dom.element('footer[role="contentinfo"]');
const space = rect.top + $footer.clientHeight;
const space = rect.top + $footer.clientHeight + border;
const height = e.detail.height - space;
this.set('height', Math.max(0, height));
this.updateItems();
this.updateScrollPosition();
}
const width = e.detail.width;
const len = get(this, 'columns.length');
switch (true) {
case width > 1013:
if (len != 4) {
set(this, 'columns', [25, 25, 25, 25]);
}
break;
case width > 744:
if (len != 3) {
set(this, 'columns', [33, 33, 34]);
}
break;
case width > 487:
if (len != 2) {
set(this, 'columns', [50, 50]);
}
break;
case width < 488:
if (len != 1) {
set(this, 'columns', [100]);
}
}
if (len !== get(this, 'columns.length')) {
this._cellLayout = this['cell-layout'] = new PercentageColumns(
get(this, 'items.length'),
get(this, 'columns'),
get(this, 'cellHeight')
);
}
},
});

View File

@ -14,9 +14,26 @@ export default Controller.extend(WithEventSource, WithSearching, {
};
this._super(...arguments);
},
searchable: computed('items.[]', function() {
searchable: computed('services.[]', function() {
return get(this, 'searchables.service')
.add(this.items)
.add(this.services)
.search(this.terms);
}),
services: computed('items.[]', function() {
return this.items.filter(function(item) {
return item.Kind === 'consul';
});
}),
proxies: computed('items.[]', function() {
return this.items.filter(function(item) {
return item.Kind === 'connect-proxy';
});
}),
withProxies: computed('proxies', function() {
const proxies = {};
this.proxies.forEach(item => {
proxies[item.Name.replace('-proxy', '')] = true;
});
return proxies;
}),
});

View File

@ -0,0 +1,16 @@
import { helper } from '@ember/component/helper';
export function healthChecks([item], hash) {
switch (true) {
case item.ChecksCritical !== 0:
return 'critical';
case item.ChecksWarning !== 0:
return 'warning';
case item.ChecksPassing !== 0:
return 'passing';
default:
return 'empty';
}
}
export default helper(healthChecks);

View File

@ -13,6 +13,7 @@ export default Model.extend({
return [];
},
}),
InstanceCount: attr('number'),
Kind: attr('string'),
ExternalSources: attr(),
Meta: attr(),

View File

@ -153,6 +153,7 @@ $sub-right-svg: url('data:image/svg+xml;charset=UTF-8,<svg viewBox="0 0 24 24" f
$support-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="M2 12C2 6.477 6.477 2 12 2c5.52.006 9.994 4.48 10 10 0 5.523-4.477 10-10 10S2 17.523 2 12zm17.83-2.588a.208.208 0 0 0 .027-.19 8.376 8.376 0 0 0-5.079-5.079.209.209 0 0 0-.278.197v3.213c0 .074.04.142.102.18.68.416 1.251.988 1.667 1.667a.21.21 0 0 0 .179.1h3.213a.208.208 0 0 0 .17-.088zM12 15.333a3.333 3.333 0 1 1 0-6.666 3.333 3.333 0 0 1 0 6.666zM9.412 4.17a.21.21 0 0 0-.19-.027A8.376 8.376 0 0 0 4.14 9.227a.206.206 0 0 0 .026.19.21.21 0 0 0 .172.083h3.213a.21.21 0 0 0 .181-.102c.416-.68.988-1.25 1.667-1.666a.21.21 0 0 0 .1-.179V4.34a.21.21 0 0 0-.088-.17zM4.143 14.778a.207.207 0 0 1 .196-.278h3.213a.21.21 0 0 1 .179.1c.416.68.987 1.25 1.666 1.667a.21.21 0 0 1 .1.178v3.213a.208.208 0 0 1-.278.196 8.376 8.376 0 0 1-5.076-5.076zm10.446 5.054a.208.208 0 0 0 .19.026 8.376 8.376 0 0 0 5.072-5.077.208.208 0 0 0-.192-.277h-3.214a.21.21 0 0 0-.178.1A5.042 5.042 0 0 1 14.6 16.27a.209.209 0 0 0-.1.178v3.214c0 .067.033.13.088.17z" fill="%23000"/></svg>');
$swap-horizontal-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="M6.99 11L3 15l3.99 4v-3H14v-2H6.99v-3zM21 9l-3.99-4v3H10v2h7.01v3L21 9z" fill="%23000"/></svg>');
$swap-vertical-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="M16 17.01V10h-2v7.01h-3L15 21l4-3.99h-3zM9 3L5 6.99h3V14h2V6.99h3L9 3z" fill="%23000"/></svg>');
$tag-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="M9.0180508,1.53059144 C9.95215711,0.826186691 11.2210727,0.822856755 12.1586551,1.52234975 L17.5225102,5.52410888 C18.2146301,6.04047204 18.6243687,6.86710244 18.6243687,7.7470623 L18.6243687,17.2546969 C18.6243687,18.7708859 17.4304613,20 15.957702,20 L5.29103534,20 C3.818276,20 2.62436867,18.7708859 2.62436867,17.2546969 L2.62436867,7.74412605 C2.62436867,6.9274347 2.97732776,6.15640255 3.58187865,5.63682791 L3.71523922,5.52941433 L9.0180508,1.53059144 Z M4.97500563,7.08324321 C4.75460594,7.25178538 4.62436867,7.51991876 4.62436867,7.80513698 L4.62436867,17.1051579 C4.62436867,17.5993656 5.0081246,18 5.48151153,18 L15.7672258,18 C16.2406127,18 16.6243687,17.5993656 16.6243687,17.1051579 L16.6243687,7.80800823 C16.6243687,7.52118194 16.492667,7.2517386 16.2701999,7.08342805 L11.0979111,3.1702619 C10.7965453,2.94225948 10.3886795,2.94334489 10.0884311,3.17294831 L4.97500563,7.08324321 Z M10.4779221,10.732233 C11.8586339,10.732233 12.9779221,9.61294492 12.9779221,8.23223305 C12.9779221,6.85152117 11.8586339,5.73223305 10.4779221,5.73223305 C9.09721019,5.73223305 7.97792206,6.85152117 7.97792206,8.23223305 C7.97792206,9.61294492 9.09721019,10.732233 10.4779221,10.732233 Z" fill="%23000"/></svg>');
$terraform-logo-color-svg: url('data:image/svg+xml;charset=UTF-8,<svg viewBox="0 0 16 18" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path fill="%235C4EE5" d="M5.51 3.15l4.886 2.821v5.644L5.509 8.792z"/><path fill="%234040B2" d="M10.931 5.971v5.644l4.888-2.823V3.15z"/><path fill="%235C4EE5" d="M.086 0v5.642l4.887 2.823V2.82zM5.51 15.053l4.886 2.823v-5.644l-4.887-2.82z"/></g></svg>');
$trash-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="M21 4v2H3V4h6l1-1h4l1 1h6zm-4 15V7h2v12c0 1.1-.9 2-2 2H7c-1.1 0-2-.9-2-2V7h2v12h10zm-8-2h2V7H9v10zm6 0h-2V7h2v10z" fill="%23000"/></svg>');
$tune-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="M3 17v2h6v-2H3zM3 5v2h10V5H3zm10 16v-2h8v-2h-8v-2h-2v6h2zM7 9v2H3v2h4v2h2V9H7zm14 4v-2H11v2h10zm-6-4h2V7h4V5h-4V3h-2v6z" fill="%23000"/></svg>');

View File

@ -1538,6 +1538,16 @@
mask-image: $swap-vertical-svg;
}
%with-tag-icon {
@extend %with-icon;
background-image: $tag-svg;
}
%with-tag-mask {
@extend %with-mask;
-webkit-mask-image: $tag-svg;
mask-image: $tag-svg;
}
%with-terraform-logo-color-icon {
@extend %with-icon;
background-image: $terraform-logo-color-svg;

View File

@ -0,0 +1,13 @@
@import './layout';
@import './skin';
%composite-row a:hover,
%composite-row a:focus,
%composite-row a:active {
@extend %composite-row-intent;
}
%composite-row > a > span {
@extend %composite-row-header;
}
%composite-row > a > ul {
@extend %composite-row-detail;
}

View File

@ -0,0 +1,22 @@
%composite-row a {
display: block;
box-sizing: border-box;
padding: 12px;
padding-right: 0;
border: 1px solid;
border-bottom: 0;
}
%composite-row-intent {
border: 1px solid;
position: relative;
}
%composite-row-detail {
display: flex;
flex-wrap: nowrap;
}
%composite-row-detail * {
white-space: nowrap;
}
%composite-row-detail > li:not(:first-child) {
margin-left: 12px;
}

View File

@ -0,0 +1,18 @@
%composite-row {
list-style-type: none;
}
%composite-row a {
border-top-color: $gray-200;
border-right-color: transparent;
border-left-color: transparent;
}
%composite-row-intent {
border-color: $gray-200;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
%composite-row-header {
color: $black;
}
%composite-row-detail {
color: $gray-500;
}

View File

@ -0,0 +1,11 @@
@import './consul-service-list/index';
.consul-service-list > ul {
@extend %consul-service-list;
}
%consul-service-list > li {
@extend %consul-service-row;
}
%consul-service-row {
@extend %composite-row;
}

View File

@ -0,0 +1,2 @@
@import './layout';
@import './skin';

View File

@ -0,0 +1,21 @@
%consul-service-list {
// Used for every DOM-Recycle scroll pane
// TODO: Refactor to have all this DOM-Recycle styling in one place
overflow-x: hidden !important;
}
%consul-service-row > a > span:first-child {
margin-right: 4px;
width: 18px;
height: 18px;
}
%consul-service-row > a > span:nth-child(3) {
margin-left: 4px;
width: 16px;
height: 16px;
}
%consul-service-row > a > ul {
margin-left: 26px;
}
%consul-service-row .proxy::before {
margin-right: 4px;
}

View File

@ -0,0 +1,43 @@
%consul-service-row > a > span:nth-child(2) {
font-size: 1.125rem;
font-weight: $typo-weight-medium;
}
%consul-service-row > a > span:first-child,
%consul-service-row > a > span:nth-child(3) {
@extend %as-pseudo;
}
%consul-service-row .empty {
@extend %with-minus-square-fill-color-mask;
background-color: #7c8797;
}
%consul-service-row .kubernetes {
@extend %with-kubernetes-logo-color-icon;
}
%consul-service-row .terraform {
@extend %with-terraform-logo-color-icon;
}
%consul-service-row .nomad {
@extend %with-nomad-logo-color-icon;
}
%consul-service-row .consul {
@extend %with-consul-logo-color-icon;
}
%consul-service-row .aws {
@extend %with-logo-aws-color-icon;
}
%consul-service-row .passing {
@extend %with-check-circle-fill-color-mask;
background-color: $green-500;
}
%consul-service-row .warning {
@extend %with-alert-triangle-color-mask;
background-color: $orange-500;
}
%consul-service-row .critical {
@extend %with-cancel-square-fill-color-mask;
background-color: $red-500;
}
%consul-service-row .proxy::before {
@extend %with-swap-horizontal-mask, %as-pseudo;
background-color: $gray-500;
}

View File

@ -0,0 +1,39 @@
.unhealthy > div,
.healthy > div {
@extend %card-grid;
}
.grid-collection {
height: 500px;
position: relative;
}
.healthy > div {
width: calc(100% + 23px);
min-height: 500px;
}
.unhealthy > div {
margin-bottom: 20px;
}
.healthy > div > ul > li {
padding-right: 23px;
padding-bottom: 20px;
}
%card-grid > ul,
%card-grid > ol {
list-style-type: none;
display: grid;
grid-auto-rows: 12px;
}
@media #{$--fixed-grid} {
%card-grid > ul,
%card-grid > ol {
grid-gap: 20px 20px;
grid-template-columns: repeat(4, minmax(220px, 1fr));
}
}
@media #{$--lt-fixed-grid} {
%card-grid > ul,
%card-grid > ol {
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
grid-gap: 20px 2%;
}
}

View File

@ -3,6 +3,7 @@
@import './anchors';
@import './progress';
@import './buttons';
@import './composite-row';
@import './secret-button';
@import './tabs';
@import './pill';
@ -31,6 +32,8 @@
@import './tabular-details';
@import './tabular-collection';
@import './list-collection';
@import './grid-collection';
@import './consul-service-list';
/**/

View File

@ -1,39 +1,4 @@
.unhealthy > div,
.healthy > div {
@extend %card-grid;
}
.list-collection {
height: 500px;
position: relative;
}
.healthy > div {
width: calc(100% + 23px);
min-height: 500px;
}
.unhealthy > div {
margin-bottom: 20px;
}
.healthy > div > ul > li {
padding-right: 23px;
padding-bottom: 20px;
}
%card-grid > ul,
%card-grid > ol {
list-style-type: none;
display: grid;
grid-auto-rows: 12px;
}
@media #{$--fixed-grid} {
%card-grid > ul,
%card-grid > ol {
grid-gap: 20px 20px;
grid-template-columns: repeat(4, minmax(220px, 1fr));
}
}
@media #{$--lt-fixed-grid} {
%card-grid > ul,
%card-grid > ol {
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
grid-gap: 20px 2%;
}
}

View File

@ -1,6 +1,5 @@
@import '../base/components/pill/index';
td strong,
%tag-list span {
td strong {
@extend %pill;
margin-right: 3px;
}

View File

@ -5,17 +5,17 @@
// the default definition list layout used in edit pages
// ideally we'd be more specific with those to say
// only add padding to dl's in edit pages
%tag-list::before {
@extend %with-tag-mask, %as-pseudo;
transform: rotate(-45deg);
margin-right: 4px;
}
%tag-list dd {
display: inline-flex;
padding-left: 0;
flex-wrap: wrap;
justify-content: space-between;
padding-left: 0px;
}
%tag-list dd:after {
content: '';
flex: auto;
}
%tag-list dd > * {
margin-right: 3px;
margin-bottom: 10px;
%tag-list dd > *:not(:last-child)::after {
content: ', ';
white-space: pre;
display: inline;
}

View File

@ -0,0 +1,3 @@
%tag-list::before {
background-color: $gray-500;
}

View File

@ -45,7 +45,7 @@
<h2>Healthy Nodes</h2>
<ChangeableSet @dispatcher={{searchableHealthy}}>
<BlockSlot @name="set" as |healthy|>
<ListCollection @cellHeight={{92}} @items={{healthy}} as |item index|>
<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)}}
@ -53,7 +53,7 @@
{{/if}}
</BlockSlot>
</HealthcheckedResource>
</ListCollection>
</GridCollection>
</BlockSlot>
<BlockSlot @name="empty">
<p>

View File

@ -2,7 +2,7 @@
<AppView @class="service list">
<BlockSlot @name="header">
<h1>
Services <em>{{format-number items.length}} total</em>
Services <em>{{format-number services.length}} total</em>
</h1>
<label for="toolbar-toggle"></label>
</BlockSlot>
@ -14,7 +14,15 @@
<BlockSlot @name="content">
<ChangeableSet @dispatcher={{searchable}}>
<BlockSlot @name="set" as |filtered|>
<ConsulServiceList @routeName="dc.services.show" @items={{filtered}} />
<ConsulServiceList @routeName="dc.services.show" @items={{filtered}}>
<BlockSlot @name="metadata" as |item|>
<ul>
<li>{{format-number item.InstanceCount}} {{pluralize item.InstanceCount 'Instance' without-count=true}}</li>
{{#if (get withProxies item.Name)}}<li class="proxy">connected with proxy</li>{{/if}}
<li><TagList @items={{item.Tags}}/></li>
</ul>
</BlockSlot>
</ConsulServiceList>
</BlockSlot>
<BlockSlot @name="empty">
<p>

View File

@ -115,8 +115,8 @@
"jsonlint": "^1.6.3",
"lint-staged": "^9.2.5",
"loader.js": "^4.7.0",
"ngraph.graph": "^18.0.3",
"mnemonist": "^0.30.0",
"ngraph.graph": "^18.0.3",
"node-sass": "^4.9.3",
"pretender": "^3.2.0",
"prettier": "^1.10.2",

View File

@ -129,15 +129,21 @@ Feature: components / catalog-filter
Given 1 datacenter model with the value "dc-1"
And 3 service models from yaml
---
- Tags: ['one', 'two', 'three']
- Name: Service-0
Kind: consul
Tags: ['one', 'two', 'three']
ChecksPassing: 0
ChecksWarning: 0
ChecksCritical: 1
- Tags: ['two', 'three']
- Name: Service-1
Kind: consul
Tags: ['two', 'three']
ChecksPassing: 0
ChecksWarning: 1
ChecksCritical: 0
- Tags: ['three']
- Name: Service-2
Kind: consul
Tags: ['three']
ChecksPassing: 1
ChecksWarning: 0
ChecksCritical: 0

View File

@ -6,7 +6,15 @@ Feature: dc / error: Recovering from a dc 500 error
- dc-1
- dc-500
---
And 3 service models
And 3 service models from yaml
---
- Name: Service-0
Kind: consul
- Name: Service-1
Kind: consul
- Name: Service-2
Kind: consul
---
And the url "/v1/internal/ui/services" responds with a 500 status
When I visit the services page for yaml
---

View File

@ -23,7 +23,6 @@ Feature: dc / list-blocking
Where:
------------------------------------------------
| Page | Model | Url |
| services | service | services |
| nodes | node | nodes |
| intentions | intention | intentions |
------------------------------------------------

View File

@ -13,7 +13,6 @@ Feature: dc / list: List Models
Where:
-------------------------------------------------
| Model | Page | Url |
| service | services | /dc-1/services |
| node | nodes | /dc-1/nodes |
| kv | kvs | /dc-1/kv |
# | acl | acls | /dc-1/acls |

View File

@ -13,7 +13,22 @@ Feature: dc / nspaces / manage : Managing Namespaces
---
- dc-1
---
And 6 service models
And 6 service models from yaml
---
- Name: Service-0
Kind: consul
- Name: Service-1
Kind: consul
- Name: Service-2
Kind: consul
- Name: Service-3
Kind: consul
- Name: Service-4
Kind: consul
- Name: Service-5
Kind: consul
---
When I visit the services page for yaml
---
dc: dc-1

View File

@ -6,7 +6,21 @@ Feature: dc / services / dc-switch : Switching Datacenters
- dc-1
- dc-2
---
And 6 service models
And 6 service models from yaml
---
- Name: Service-0
Kind: consul
- Name: Service-1
Kind: consul
- Name: Service-2
Kind: consul
- Name: Service-3
Kind: consul
- Name: Service-4
Kind: consul
- Name: Service-5
Kind: consul
---
When I visit the services page for yaml
---
dc: dc-1

View File

@ -2,24 +2,24 @@
Feature: dc / services / index: List Services
Scenario:
Given 1 datacenter model with the value "dc-1"
And 6 service models from yaml
And 4 service models from yaml
---
- Name: Service 1
Kind: consul
ExternalSources:
- consul
- Name: Service 2
Kind: consul
ExternalSources:
- nomad
- Name: Service 3
Kind: consul
ExternalSources:
- terraform
- Name: Service 4
Kind: consul
ExternalSources:
- kubernetes
- Name: Service 5
ExternalSources: []
- Name: Service 6
ExternalSources: ~
---
When I visit the services page for yaml
---
@ -27,14 +27,12 @@ Feature: dc / services / index: List Services
---
Then the url should be /dc-1/services
And the title should be "Services - Consul"
Then I see 6 service models
Then I see 4 service models
And I see externalSource on the services like yaml
---
- consul
- nomad
- terraform
- kubernetes
- ~
- ~
---

View File

@ -0,0 +1,26 @@
@setupApplicationTest
Feature: dc / services / list blocking
Scenario: Viewing the listing pages for service
Given 1 datacenter model with the value "dc-1"
Given 3 service models from yaml
---
- Name: Service-0
Kind: consul
- Name: Service-1
Kind: consul
- Name: Service-2
Kind: consul
---
And a network latency of 100
When I visit the services page for yaml
---
dc: dc-1
---
Then the url should be /dc-1/services
And pause until I see 3 service models
And an external edit results in 5 service models
And pause until I see 3 service models
And an external edit results in 1 service model
And pause until I see 1 service model
And an external edit results in 0 service models
And pause until I see 0 service models

View File

@ -0,0 +1,20 @@
@setupApplicationTest
Feature: dc / services / list
Scenario: Listing service
Given 1 datacenter model with the value "dc-1"
And 3 service models from yaml
---
- Name: Service-0
Kind: consul
- Name: Service-1
Kind: consul
- Name: Service-2
Kind: consul
---
When I visit the services page for yaml
---
dc: dc-1
---
Then the url should be /dc-1/services
Then I see 3 service models

View File

@ -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);
});
}

View File

@ -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);
});
}

View File

@ -69,7 +69,7 @@ export default {
index: create(index(visitable, collection)),
dcs: create(dcs(visitable, clickable, attribute, collection)),
services: create(
services(visitable, clickable, attribute, collection, page, catalogFilter, radiogroup)
services(visitable, clickable, text, attribute, collection, page, catalogFilter, radiogroup)
),
service: create(service(visitable, attribute, collection, text, catalogFilter, tabgroup)),
instance: create(instance(visitable, attribute, collection, text, tabgroup)),

View File

@ -1,11 +1,12 @@
export default function(visitable, clickable, attribute, collection, page, filter) {
export default function(visitable, clickable, text, attribute, collection, page, filter) {
const service = {
name: text('a span:nth-child(2)'),
service: clickable('a'),
externalSource: attribute('data-test-external-source', '[data-test-external-source]'),
};
return {
visit: visitable('/:dc/services'),
services: collection('[data-test-service]', {
name: attribute('data-test-service'),
service: clickable('a'),
externalSource: attribute('data-test-external-source', 'a span'),
}),
services: collection('.consul-service-list > ul > li:not(:first-child)', service),
dcs: collection('[data-test-datacenter-picker]', {
name: clickable('a'),
}),

View File

@ -1211,9 +1211,9 @@
js-yaml "^3.13.1"
"@hashicorp/consul-api-double@^2.6.2":
version "2.12.0"
resolved "https://registry.yarnpkg.com/@hashicorp/consul-api-double/-/consul-api-double-2.12.0.tgz#725078f770bbd0ef75a5f2498968c5c8891f90a2"
integrity sha512-8OcgesUjWQ8AjaXzbz3tGJQn1kM0sN6pLidGM7isNPUyYmIjIEXQzaeUQYzsfv0N2Ko9ZuOXYUsaBl8IK1KGow==
version "2.14.0"
resolved "https://registry.yarnpkg.com/@hashicorp/consul-api-double/-/consul-api-double-2.14.0.tgz#ecef725fc22490011a671bc0a285a16013ca5e53"
integrity sha512-1rGMg/XSHR2ROr8a7OVEwOUy8UWuYdNUMijMxCuFHR201vDAGK9EDmkJCPF2PfYsDrcsiyb/0dxIL6Mba9p32Q==
"@hashicorp/ember-cli-api-double@^3.0.2":
version "3.0.2"