ui: Upstream Instance Search and Sort (#9172)
* ui: Add predicate, comparator and necessary files for the search/sort * Implement search and sort for upstream instance list * ui: Tweak CSS so its all part of the component * Remove the old proxy test attribute
This commit is contained in:
parent
a5bd1ba323
commit
f68d989d84
|
@ -1,8 +1,13 @@
|
|||
<ul data-test-proxy-upstreams class="consul-upstream-instance-list">
|
||||
{{#each @items as |item|}}
|
||||
<div
|
||||
class="consul-upstream-instance-list"
|
||||
...attributes
|
||||
>
|
||||
{{#if (gt this.items.length 0)}}
|
||||
<ul>
|
||||
{{#each this.items as |item|}}
|
||||
<li>
|
||||
<div class="header">
|
||||
<p data-test-destination-name>
|
||||
<p>
|
||||
{{item.DestinationName}}
|
||||
</p>
|
||||
</div>
|
||||
|
@ -34,19 +39,28 @@
|
|||
</dl>
|
||||
{{/if}}
|
||||
{{#if (gt item.LocalBindPort 0)}}
|
||||
{{#let (concat (or item.LocalBindAddress '127.0.0.1') ':' item.LocalBindPort) as |combinedAddress|}}
|
||||
{{#let (concat (or item.LocalBindAddress '127.0.0.1') ':' item.LocalBindPort) as |combinedAddress|}}
|
||||
<dl class="local-bind-address">
|
||||
<dt>
|
||||
<span>
|
||||
Address
|
||||
</span>
|
||||
</dt>
|
||||
<dd>
|
||||
<CopyButton
|
||||
@value={{combinedAddress}}
|
||||
@name="Address"
|
||||
/>
|
||||
<span>
|
||||
{{combinedAddress}}
|
||||
</span>
|
||||
</span>
|
||||
{{/let}}
|
||||
</dd>
|
||||
</dl>
|
||||
{{/let}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
{{/each}}
|
||||
</ul>
|
||||
{{else}}
|
||||
{{yield api to="empty"}}
|
||||
{{/if}}
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
import Component from '@glimmer/component';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { sort } from '@ember/object/computed';
|
||||
|
||||
export default class ConsulUpstreamInstanceList extends Component {
|
||||
@service('sort') sort;
|
||||
@service('search') search;
|
||||
|
||||
@sort('searched', 'comparator') sorted;
|
||||
|
||||
get items() {
|
||||
return this.sorted;
|
||||
}
|
||||
get searched() {
|
||||
if (typeof this.args.search === 'undefined') {
|
||||
return this.args.items;
|
||||
}
|
||||
const predicate = this.search.predicate('upstream-instance');
|
||||
return this.args.items.filter(predicate(this.args.search));
|
||||
}
|
||||
get comparator() {
|
||||
return this.sort.comparator('upstream-instance')(this.args.sort);
|
||||
}
|
||||
}
|
|
@ -1,6 +1,14 @@
|
|||
.consul-upstream-instance-list > li {
|
||||
.consul-upstream-instance-list {
|
||||
li {
|
||||
@extend %composite-row;
|
||||
}
|
||||
.consul-upstream-instance-list > ul {
|
||||
border-top: 1px solid $gray-200;
|
||||
}
|
||||
dl {
|
||||
@extend %icon-definition;
|
||||
}
|
||||
dl.datacenter dt::before {
|
||||
@extend %with-user-organization-mask, %as-pseudo;
|
||||
}
|
||||
dl.nspace dt::before {
|
||||
@extend %with-folder-outline-mask, %as-pseudo;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
export default (collection, text) => (scope = '.consul-upstream-instance-list') => {
|
||||
return {
|
||||
scope,
|
||||
item: collection('li', {
|
||||
name: text('.header p'),
|
||||
nspace: text('.nspace dd'),
|
||||
datacenter: text('.datacenter dd'),
|
||||
localAddress: text('.local-address dd'),
|
||||
}),
|
||||
};
|
||||
};
|
|
@ -0,0 +1,41 @@
|
|||
<form
|
||||
class="consul-upstream-instance-search-bar filter-bar"
|
||||
...attributes
|
||||
>
|
||||
<FreetextFilter
|
||||
@onsearch={{action @onsearch}}
|
||||
@value={{@search}}
|
||||
@placeholder="Search"
|
||||
/>
|
||||
<div class="sort">
|
||||
{{#let (or @sort 'DestinationName:asc') as |sort|}}
|
||||
<PopoverSelect
|
||||
class="type-sort"
|
||||
data-test-sort-control
|
||||
@position="right"
|
||||
@onchange={{action @onsort}}
|
||||
@multiple={{false}}
|
||||
as |components|>
|
||||
<BlockSlot @name="selected">
|
||||
<span>
|
||||
{{#let (from-entries (array
|
||||
(array "DestinationName:asc" "A to Z")
|
||||
(array "DestinationName:desc" "Z to A")
|
||||
))
|
||||
as |selectable|}}
|
||||
{{get selectable sort}}
|
||||
{{/let}}
|
||||
</span>
|
||||
</BlockSlot>
|
||||
<BlockSlot @name="options">
|
||||
{{#let components.Optgroup components.Option as |Optgroup Option|}}
|
||||
<Optgroup @label="Service Name">
|
||||
<Option @value="DestinationName:asc" @selected={{eq "DestinationName:asc" sort}}>A to Z</Option>
|
||||
<Option @value="DestinationName:desc" @selected={{eq "DestinationName:desc" sort}}>Z to A</Option>
|
||||
</Optgroup>
|
||||
{{/let}}
|
||||
</BlockSlot>
|
||||
</PopoverSelect>
|
||||
{{/let}}
|
||||
</div>
|
||||
</form>
|
|
@ -0,0 +1,11 @@
|
|||
import Controller from '@ember/controller';
|
||||
|
||||
export default class DcServicesInstanceUpstreamsController extends Controller {
|
||||
queryParams = {
|
||||
sortBy: 'sort',
|
||||
search: {
|
||||
as: 'filter',
|
||||
replace: true,
|
||||
},
|
||||
};
|
||||
}
|
|
@ -1,6 +1,14 @@
|
|||
import Route from 'consul-ui/routing/route';
|
||||
|
||||
export default class UpstreamsRoute extends Route {
|
||||
queryParams = {
|
||||
sortBy: 'sort',
|
||||
search: {
|
||||
as: 'filter',
|
||||
replace: true,
|
||||
},
|
||||
};
|
||||
|
||||
model() {
|
||||
const parent = this.routeName
|
||||
.split('.')
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
export default () => term => item => {
|
||||
const lowerTerm = term.toLowerCase();
|
||||
return Object.entries(item)
|
||||
.filter(([key, value]) => key !== 'DestinationType')
|
||||
.some(
|
||||
([key, value]) =>
|
||||
value
|
||||
.toString()
|
||||
.toLowerCase()
|
||||
.indexOf(lowerTerm) !== -1
|
||||
);
|
||||
};
|
|
@ -1,6 +1,8 @@
|
|||
import Service from '@ember/service';
|
||||
|
||||
import intention from 'consul-ui/search/predicates/intention';
|
||||
import upstreamInstance from 'consul-ui/search/predicates/upstream-instance';
|
||||
|
||||
import token from 'consul-ui/search/filters/token';
|
||||
import policy from 'consul-ui/search/filters/policy';
|
||||
import role from 'consul-ui/search/filters/role';
|
||||
|
@ -29,6 +31,7 @@ const searchables = {
|
|||
};
|
||||
const predicates = {
|
||||
intention: intention(),
|
||||
['upstream-instance']: upstreamInstance(),
|
||||
};
|
||||
export default class SearchService extends Service {
|
||||
searchable(name) {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import Service from '@ember/service';
|
||||
import service from 'consul-ui/sort/comparators/service';
|
||||
import serviceInstance from 'consul-ui/sort/comparators/service-instance';
|
||||
import upstreamInstance from 'consul-ui/sort/comparators/upstream-instance';
|
||||
import kv from 'consul-ui/sort/comparators/kv';
|
||||
import check from 'consul-ui/sort/comparators/check';
|
||||
import intention from 'consul-ui/sort/comparators/intention';
|
||||
|
@ -13,6 +14,7 @@ import node from 'consul-ui/sort/comparators/node';
|
|||
const comparators = {
|
||||
service: service(),
|
||||
serviceInstance: serviceInstance(),
|
||||
['upstream-instance']: upstreamInstance(),
|
||||
kv: kv(),
|
||||
check: check(),
|
||||
intention: intention(),
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
const directionify = arr => {
|
||||
return arr.reduce((prev, item) => prev.concat([`${item}:asc`, `${item}:desc`]), []);
|
||||
};
|
||||
export default () => key => {
|
||||
const comparables = directionify(['DestinationName']);
|
||||
return [comparables.find(item => item === key) || comparables[0]];
|
||||
};
|
|
@ -56,10 +56,12 @@
|
|||
@import 'consul-ui/components/modal-dialog';
|
||||
|
||||
@import 'consul-ui/components/consul/discovery-chain';
|
||||
@import 'consul-ui/components/consul/upstream-instance/list';
|
||||
@import 'consul-ui/components/consul/exposed-path/list';
|
||||
@import 'consul-ui/components/consul/external-source';
|
||||
@import 'consul-ui/components/consul/kind';
|
||||
@import 'consul-ui/components/consul/intention';
|
||||
|
||||
@import 'consul-ui/components/role-selector';
|
||||
@import 'consul-ui/components/topology-metrics';
|
||||
@import 'consul-ui/components/topology-metrics/popover';
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
@import './composite-row/index';
|
||||
|
||||
.consul-upstream-instance-list > li,
|
||||
.list-collection > ul > li:not(:first-child) {
|
||||
@extend %composite-row;
|
||||
}
|
||||
|
@ -33,8 +32,7 @@
|
|||
.consul-service-instance-list .detail {
|
||||
overflow-x: visible !important;
|
||||
}
|
||||
.consul-intention-permission-list > ul,
|
||||
.consul-upstream-instance-list > ul {
|
||||
.consul-intention-permission-list > ul {
|
||||
border-top: 1px solid $gray-200;
|
||||
}
|
||||
.consul-service-instance-list .port dt,
|
||||
|
|
|
@ -1,7 +1,37 @@
|
|||
<div class="tab-section">
|
||||
<div role="tabpanel">
|
||||
{{#let (or sortBy "DestinationName:asc") as |sort|}}
|
||||
{{#if (gt proxy.Service.Proxy.Upstreams.length 0)}}
|
||||
<Consul::UpstreamInstance::List @items={{proxy.Service.Proxy.Upstreams}} @dc={{dc}} @nspace={{nspace}} />
|
||||
<input type="checkbox" id="toolbar-toggle" />
|
||||
<Consul::UpstreamInstance::SearchBar
|
||||
@search={{search}}
|
||||
@onsearch={{action (mut search) value="target.value"}}
|
||||
|
||||
@sort={{sort}}
|
||||
@onsort={{action (mut sortBy) value="target.selected"}}
|
||||
/>
|
||||
<Consul::UpstreamInstance::List
|
||||
@search={{search}}
|
||||
@sort={{sort}}
|
||||
@items={{proxy.Service.Proxy.Upstreams}}
|
||||
@dc={{dc}}
|
||||
@nspace={{nspace}}
|
||||
>
|
||||
<:empty>
|
||||
<EmptyState>
|
||||
<BlockSlot @name="body">
|
||||
<p>
|
||||
{{#if search.length}}
|
||||
No upstreams where found matching that search.
|
||||
{{else}}
|
||||
This service has no upstreams.
|
||||
{{/if}}
|
||||
</p>
|
||||
</BlockSlot>
|
||||
</EmptyState>
|
||||
</:empty>
|
||||
</Consul::UpstreamInstance::List>
|
||||
{{/if}}
|
||||
{{/let}}
|
||||
</div>
|
||||
</div>
|
|
@ -40,6 +40,7 @@ import popoverSelectFactory from 'consul-ui/components/popover-select/pageobject
|
|||
import morePopoverMenuFactory from 'consul-ui/components/more-popover-menu/pageobject';
|
||||
|
||||
import tokenListFactory from 'consul-ui/components/token-list/pageobject';
|
||||
import consulUpstreamInstanceListFactory from 'consul-ui/components/consul/upstream-instance/list/pageobject';
|
||||
import consulTokenListFactory from 'consul-ui/components/consul/token/list/pageobject';
|
||||
import consulRoleListFactory from 'consul-ui/components/consul/role/list/pageobject';
|
||||
import consulPolicyListFactory from 'consul-ui/components/consul/policy/list/pageobject';
|
||||
|
@ -94,6 +95,7 @@ const morePopoverMenu = morePopoverMenuFactory(clickable);
|
|||
const popoverSelect = popoverSelectFactory(clickable, collection);
|
||||
const emptyState = emptyStateFactory(isPresent);
|
||||
|
||||
const consulUpstreamInstanceList = consulUpstreamInstanceListFactory(collection, text);
|
||||
const consulIntentionList = consulIntentionListFactory(
|
||||
collection,
|
||||
clickable,
|
||||
|
@ -159,7 +161,9 @@ export default {
|
|||
service: create(
|
||||
service(visitable, attribute, collection, text, consulIntentionList, catalogToolbar, tabgroup)
|
||||
),
|
||||
instance: create(instance(visitable, attribute, collection, text, tabgroup)),
|
||||
instance: create(
|
||||
instance(visitable, alias, attribute, collection, text, tabgroup, consulUpstreamInstanceList)
|
||||
),
|
||||
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)),
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export default function(visitable, attribute, collection, text, tabs) {
|
||||
export default function(visitable, alias, attribute, collection, text, tabs, upstreams) {
|
||||
return {
|
||||
visit: visitable('/:dc/services/:service/instances/:node/:id'),
|
||||
externalSource: attribute('data-test-external-source', '[data-test-external-source]', {
|
||||
|
@ -6,9 +6,8 @@ export default function(visitable, attribute, collection, text, tabs) {
|
|||
}),
|
||||
tabs: tabs('tab', ['health-checks', 'upstreams', 'exposed-paths', 'addresses', 'tags-&-meta']),
|
||||
checks: collection('[data-test-checks] li'),
|
||||
upstreams: collection('[data-test-proxy-upstreams] > li', {
|
||||
name: text('[data-test-destination-name]'),
|
||||
}),
|
||||
upstreams: alias('upstreamInstances.item'),
|
||||
upstreamInstances: upstreams(),
|
||||
exposedPaths: collection('[data-test-proxy-exposed-paths] > tbody tr', {
|
||||
combinedAddress: text('[data-test-combined-address]'),
|
||||
}),
|
||||
|
|
Loading…
Reference in New Issue