Skip to content

Commit

Permalink
ui: Redesign Service List page (#7605)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
kaxcode authored and John Cowen committed Apr 30, 2020
1 parent d098807 commit 3415206
Show file tree
Hide file tree
Showing 40 changed files with 482 additions and 208 deletions.
45 changes: 13 additions & 32 deletions ui-v2/app/components/consul-service-list/index.hbs
Original file line number Diff line number Diff line change
@@ -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}}>
<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}}
{{item.Name}}
</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 @cellHeight={{73}} @items={{items}} class="consul-service-list" as |item index|>
<a href={{href-to routeName item.Name}}>
<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>
</ListCollection>
{{/if}}
63 changes: 2 additions & 61 deletions ui-v2/app/components/consul-service-list/index.js
Original file line number Diff line number Diff line change
@@ -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')));
}),
});
6 changes: 6 additions & 0 deletions ui-v2/app/components/grid-collection/index.hbs
Original file line number Diff line number Diff line change
@@ -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>
78 changes: 78 additions & 0 deletions ui-v2/app/components/grid-collection/index.js
Original file line number Diff line number Diff line change
@@ -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')
);
}
},
});
38 changes: 4 additions & 34 deletions ui-v2/app/components/list-collection/index.js
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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);
Expand All @@ -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')
);
}
},
});
21 changes: 19 additions & 2 deletions ui-v2/app/controllers/dc/services/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}),
});
16 changes: 16 additions & 0 deletions ui-v2/app/helpers/service/health-checks.js
Original file line number Diff line number Diff line change
@@ -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);
1 change: 1 addition & 0 deletions ui-v2/app/models/service.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export default Model.extend({
return [];
},
}),
InstanceCount: attr('number'),
Kind: attr('string'),
ExternalSources: attr(),
Meta: attr(),
Expand Down
1 change: 1 addition & 0 deletions ui-v2/app/styles/base/icons/base-variables.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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>');
Expand Down
10 changes: 10 additions & 0 deletions ui-v2/app/styles/base/icons/icon-placeholders.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
13 changes: 13 additions & 0 deletions ui-v2/app/styles/components/composite-row/index.scss
Original file line number Diff line number Diff line change
@@ -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;
}
22 changes: 22 additions & 0 deletions ui-v2/app/styles/components/composite-row/layout.scss
Original file line number Diff line number Diff line change
@@ -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;
}
Loading

0 comments on commit 3415206

Please sign in to comment.