mirror of https://github.com/status-im/consul.git
ui: Add consul-service-list presentational component (#7279)
This commit moves our service list into a new presentational component, and is therefore mainly just moving things around. The main thing moved here is the logic required to resizing columns correctly is now moved to a component instead of the controller
This commit is contained in:
parent
7dc5201676
commit
771973a713
|
@ -0,0 +1,65 @@
|
||||||
|
import Component from '@ember/component';
|
||||||
|
import { get, computed } from '@ember/object';
|
||||||
|
import { htmlSafe } from '@ember/string';
|
||||||
|
|
||||||
|
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({
|
||||||
|
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')));
|
||||||
|
}),
|
||||||
|
});
|
|
@ -1,30 +1,7 @@
|
||||||
import Controller from '@ember/controller';
|
import Controller from '@ember/controller';
|
||||||
import { get, computed } from '@ember/object';
|
import { get, computed } from '@ember/object';
|
||||||
import { htmlSafe } from '@ember/string';
|
|
||||||
import WithEventSource from 'consul-ui/mixins/with-event-source';
|
import WithEventSource from 'consul-ui/mixins/with-event-source';
|
||||||
import WithSearching from 'consul-ui/mixins/with-searching';
|
import WithSearching from 'consul-ui/mixins/with-searching';
|
||||||
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 Controller.extend(WithEventSource, WithSearching, {
|
export default Controller.extend(WithEventSource, WithSearching, {
|
||||||
queryParams: {
|
queryParams: {
|
||||||
s: {
|
s: {
|
||||||
|
@ -42,39 +19,4 @@ export default Controller.extend(WithEventSource, WithSearching, {
|
||||||
.add(this.items)
|
.add(this.items)
|
||||||
.search(this.terms);
|
.search(this.terms);
|
||||||
}),
|
}),
|
||||||
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(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% - ${Math.round(this.maxWidth / 2)}px)`);
|
|
||||||
}),
|
|
||||||
maxPassing: computed('items.[]', function() {
|
|
||||||
return max(this.items, 'ChecksPassing');
|
|
||||||
}),
|
|
||||||
maxWarning: computed('items.[]', function() {
|
|
||||||
return max(this.items, 'ChecksWarning');
|
|
||||||
}),
|
|
||||||
maxCritical: computed('items.[]', function() {
|
|
||||||
return max(this.items, 'ChecksCritical');
|
|
||||||
}),
|
|
||||||
passingWidth: computed('maxPassing', function() {
|
|
||||||
return widthDeclaration(width(this.maxPassing));
|
|
||||||
}),
|
|
||||||
warningWidth: computed('maxWarning', function() {
|
|
||||||
return widthDeclaration(width(this.maxWarning));
|
|
||||||
}),
|
|
||||||
criticalWidth: computed('maxCritical', function() {
|
|
||||||
return widthDeclaration(width(this.maxCritical));
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
{{#if (gt items.length 0)}}
|
||||||
|
{{#tabular-collection items=items as |item index|}}
|
||||||
|
{{#block-slot 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>
|
||||||
|
{{/block-slot}}
|
||||||
|
{{#block-slot 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}}>
|
||||||
|
{{healthcheck-info
|
||||||
|
passing=item.ChecksPassing warning=item.ChecksWarning critical=item.ChecksCritical
|
||||||
|
passingWidth=passingWidth warningWidth=warningWidth criticalWidth=criticalWidth
|
||||||
|
}}
|
||||||
|
</td>
|
||||||
|
<td style={{remainingWidth}}>
|
||||||
|
{{tag-list items=item.Tags}}
|
||||||
|
</td>
|
||||||
|
{{/block-slot}}
|
||||||
|
{{/tabular-collection}}
|
||||||
|
{{/if}}
|
|
@ -22,40 +22,7 @@
|
||||||
{{#block-slot name='content'}}
|
{{#block-slot name='content'}}
|
||||||
{{#changeable-set dispatcher=searchable}}
|
{{#changeable-set dispatcher=searchable}}
|
||||||
{{#block-slot name='set' as |filtered|}}
|
{{#block-slot name='set' as |filtered|}}
|
||||||
{{#tabular-collection
|
{{consul-service-list routeName="dc.services.show" items=filtered}}
|
||||||
route='dc.services.show'
|
|
||||||
key='Name'
|
|
||||||
items=filtered as |item index|
|
|
||||||
}}
|
|
||||||
{{#block-slot 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>
|
|
||||||
{{/block-slot}}
|
|
||||||
{{#block-slot name='row'}}
|
|
||||||
<td data-test-service="{{item.Name}}" style={{remainingWidth}}>
|
|
||||||
<a href={{href-to 'dc.services.show' 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}}>
|
|
||||||
{{healthcheck-info
|
|
||||||
passing=item.ChecksPassing warning=item.ChecksWarning critical=item.ChecksCritical
|
|
||||||
passingWidth=passingWidth warningWidth=warningWidth criticalWidth=criticalWidth
|
|
||||||
}}
|
|
||||||
</td>
|
|
||||||
<td style={{remainingWidth}}>
|
|
||||||
{{tag-list items=item.Tags}}
|
|
||||||
</td>
|
|
||||||
{{/block-slot}}
|
|
||||||
{{/tabular-collection}}
|
|
||||||
{{/block-slot}}
|
{{/block-slot}}
|
||||||
{{#block-slot name='empty'}}
|
{{#block-slot name='empty'}}
|
||||||
<p>
|
<p>
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { module, test } from 'qunit';
|
||||||
|
import { setupRenderingTest } from 'ember-qunit';
|
||||||
|
import { render } from '@ember/test-helpers';
|
||||||
|
import hbs from 'htmlbars-inline-precompile';
|
||||||
|
|
||||||
|
module('Integration | Component | consul-service-list', 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`{{consul-service-list}}`);
|
||||||
|
|
||||||
|
assert.equal(this.element.textContent.trim(), '');
|
||||||
|
|
||||||
|
// Template block usage:
|
||||||
|
await render(hbs`
|
||||||
|
{{#consul-service-list}}{{/consul-service-list}}
|
||||||
|
`);
|
||||||
|
|
||||||
|
assert.equal(this.element.textContent.trim(), '');
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue