mirror of https://github.com/hashicorp/consul
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 attributepull/9177/head
parent
61eac21f1a
commit
6b29704027
|
@ -1,52 +1,66 @@
|
||||||
<ul data-test-proxy-upstreams class="consul-upstream-instance-list">
|
<div
|
||||||
{{#each @items as |item|}}
|
class="consul-upstream-instance-list"
|
||||||
<li>
|
...attributes
|
||||||
<div class="header">
|
>
|
||||||
<p data-test-destination-name>
|
{{#if (gt this.items.length 0)}}
|
||||||
{{item.DestinationName}}
|
<ul>
|
||||||
</p>
|
{{#each this.items as |item|}}
|
||||||
</div>
|
<li>
|
||||||
<div class="detail">
|
<div class="header">
|
||||||
{{#if (env 'CONSUL_NSPACES_ENABLED')}}
|
<p>
|
||||||
{{#if (not-eq item.DestinationType 'prepared_query')}}
|
{{item.DestinationName}}
|
||||||
<dl class="nspace">
|
</p>
|
||||||
<dt>
|
</div>
|
||||||
<Tooltip>
|
<div class="detail">
|
||||||
Namespace
|
{{#if (env 'CONSUL_NSPACES_ENABLED')}}
|
||||||
</Tooltip>
|
{{#if (not-eq item.DestinationType 'prepared_query')}}
|
||||||
</dt>
|
<dl class="nspace">
|
||||||
<dd>
|
<dt>
|
||||||
{{or item.DestinationNamespace 'default'}}
|
<Tooltip>
|
||||||
</dd>
|
Namespace
|
||||||
</dl>
|
</Tooltip>
|
||||||
{{/if}}
|
</dt>
|
||||||
|
<dd>
|
||||||
|
{{or item.DestinationNamespace 'default'}}
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
{{/if}}
|
||||||
|
{{/if}}
|
||||||
|
{{#if (and (not-eq item.Datacenter @dc) (not-eq item.Datacenter ""))}}
|
||||||
|
<dl class="datacenter">
|
||||||
|
<dt>
|
||||||
|
<Tooltip>
|
||||||
|
Datacenter
|
||||||
|
</Tooltip>
|
||||||
|
</dt>
|
||||||
|
<dd>
|
||||||
|
{{item.Datacenter}}
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
{{/if}}
|
||||||
|
{{#if (gt item.LocalBindPort 0)}}
|
||||||
|
{{#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"
|
||||||
|
/>
|
||||||
|
{{combinedAddress}}
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
{{/let}}
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
{{/each}}
|
||||||
|
</ul>
|
||||||
|
{{else}}
|
||||||
|
{{yield api to="empty"}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#if (and (not-eq item.Datacenter @dc) (not-eq item.Datacenter ""))}}
|
</div>
|
||||||
<dl class="datacenter">
|
|
||||||
<dt>
|
|
||||||
<Tooltip>
|
|
||||||
Datacenter
|
|
||||||
</Tooltip>
|
|
||||||
</dt>
|
|
||||||
<dd>
|
|
||||||
{{item.Datacenter}}
|
|
||||||
</dd>
|
|
||||||
</dl>
|
|
||||||
{{/if}}
|
|
||||||
{{#if (gt item.LocalBindPort 0)}}
|
|
||||||
{{#let (concat (or item.LocalBindAddress '127.0.0.1') ':' item.LocalBindPort) as |combinedAddress|}}
|
|
||||||
<span>
|
|
||||||
<CopyButton
|
|
||||||
@value={{combinedAddress}}
|
|
||||||
@name="Address"
|
|
||||||
/>
|
|
||||||
<span>
|
|
||||||
{{combinedAddress}}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
{{/let}}
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
{{/each}}
|
|
||||||
</ul>
|
|
||||||
|
|
|
@ -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 {
|
||||||
@extend %composite-row;
|
li {
|
||||||
|
@extend %composite-row;
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.consul-upstream-instance-list > ul {
|
|
||||||
border-top: 1px solid $gray-200;
|
|
||||||
}
|
|
|
@ -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';
|
import Route from 'consul-ui/routing/route';
|
||||||
|
|
||||||
export default class UpstreamsRoute extends Route {
|
export default class UpstreamsRoute extends Route {
|
||||||
|
queryParams = {
|
||||||
|
sortBy: 'sort',
|
||||||
|
search: {
|
||||||
|
as: 'filter',
|
||||||
|
replace: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
model() {
|
model() {
|
||||||
const parent = this.routeName
|
const parent = this.routeName
|
||||||
.split('.')
|
.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 Service from '@ember/service';
|
||||||
|
|
||||||
import intention from 'consul-ui/search/predicates/intention';
|
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 token from 'consul-ui/search/filters/token';
|
||||||
import policy from 'consul-ui/search/filters/policy';
|
import policy from 'consul-ui/search/filters/policy';
|
||||||
import role from 'consul-ui/search/filters/role';
|
import role from 'consul-ui/search/filters/role';
|
||||||
|
@ -29,6 +31,7 @@ const searchables = {
|
||||||
};
|
};
|
||||||
const predicates = {
|
const predicates = {
|
||||||
intention: intention(),
|
intention: intention(),
|
||||||
|
['upstream-instance']: upstreamInstance(),
|
||||||
};
|
};
|
||||||
export default class SearchService extends Service {
|
export default class SearchService extends Service {
|
||||||
searchable(name) {
|
searchable(name) {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import Service from '@ember/service';
|
import Service from '@ember/service';
|
||||||
import service from 'consul-ui/sort/comparators/service';
|
import service from 'consul-ui/sort/comparators/service';
|
||||||
import serviceInstance from 'consul-ui/sort/comparators/service-instance';
|
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 kv from 'consul-ui/sort/comparators/kv';
|
||||||
import check from 'consul-ui/sort/comparators/check';
|
import check from 'consul-ui/sort/comparators/check';
|
||||||
import intention from 'consul-ui/sort/comparators/intention';
|
import intention from 'consul-ui/sort/comparators/intention';
|
||||||
|
@ -13,6 +14,7 @@ import node from 'consul-ui/sort/comparators/node';
|
||||||
const comparators = {
|
const comparators = {
|
||||||
service: service(),
|
service: service(),
|
||||||
serviceInstance: serviceInstance(),
|
serviceInstance: serviceInstance(),
|
||||||
|
['upstream-instance']: upstreamInstance(),
|
||||||
kv: kv(),
|
kv: kv(),
|
||||||
check: check(),
|
check: check(),
|
||||||
intention: intention(),
|
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/modal-dialog';
|
||||||
|
|
||||||
@import 'consul-ui/components/consul/discovery-chain';
|
@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/exposed-path/list';
|
||||||
@import 'consul-ui/components/consul/external-source';
|
@import 'consul-ui/components/consul/external-source';
|
||||||
@import 'consul-ui/components/consul/kind';
|
@import 'consul-ui/components/consul/kind';
|
||||||
@import 'consul-ui/components/consul/intention';
|
@import 'consul-ui/components/consul/intention';
|
||||||
|
|
||||||
@import 'consul-ui/components/role-selector';
|
@import 'consul-ui/components/role-selector';
|
||||||
@import 'consul-ui/components/topology-metrics';
|
@import 'consul-ui/components/topology-metrics';
|
||||||
@import 'consul-ui/components/topology-metrics/popover';
|
@import 'consul-ui/components/topology-metrics/popover';
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
@import './composite-row/index';
|
@import './composite-row/index';
|
||||||
|
|
||||||
.consul-upstream-instance-list > li,
|
|
||||||
.list-collection > ul > li:not(:first-child) {
|
.list-collection > ul > li:not(:first-child) {
|
||||||
@extend %composite-row;
|
@extend %composite-row;
|
||||||
}
|
}
|
||||||
|
@ -33,8 +32,7 @@
|
||||||
.consul-service-instance-list .detail {
|
.consul-service-instance-list .detail {
|
||||||
overflow-x: visible !important;
|
overflow-x: visible !important;
|
||||||
}
|
}
|
||||||
.consul-intention-permission-list > ul,
|
.consul-intention-permission-list > ul {
|
||||||
.consul-upstream-instance-list > ul {
|
|
||||||
border-top: 1px solid $gray-200;
|
border-top: 1px solid $gray-200;
|
||||||
}
|
}
|
||||||
.consul-service-instance-list .port dt,
|
.consul-service-instance-list .port dt,
|
||||||
|
|
|
@ -1,7 +1,37 @@
|
||||||
<div class="tab-section">
|
<div class="tab-section">
|
||||||
<div role="tabpanel">
|
<div role="tabpanel">
|
||||||
|
{{#let (or sortBy "DestinationName:asc") as |sort|}}
|
||||||
{{#if (gt proxy.Service.Proxy.Upstreams.length 0)}}
|
{{#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}}
|
{{/if}}
|
||||||
|
{{/let}}
|
||||||
</div>
|
</div>
|
||||||
</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 morePopoverMenuFactory from 'consul-ui/components/more-popover-menu/pageobject';
|
||||||
|
|
||||||
import tokenListFactory from 'consul-ui/components/token-list/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 consulTokenListFactory from 'consul-ui/components/consul/token/list/pageobject';
|
||||||
import consulRoleListFactory from 'consul-ui/components/consul/role/list/pageobject';
|
import consulRoleListFactory from 'consul-ui/components/consul/role/list/pageobject';
|
||||||
import consulPolicyListFactory from 'consul-ui/components/consul/policy/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 popoverSelect = popoverSelectFactory(clickable, collection);
|
||||||
const emptyState = emptyStateFactory(isPresent);
|
const emptyState = emptyStateFactory(isPresent);
|
||||||
|
|
||||||
|
const consulUpstreamInstanceList = consulUpstreamInstanceListFactory(collection, text);
|
||||||
const consulIntentionList = consulIntentionListFactory(
|
const consulIntentionList = consulIntentionListFactory(
|
||||||
collection,
|
collection,
|
||||||
clickable,
|
clickable,
|
||||||
|
@ -159,7 +161,9 @@ export default {
|
||||||
service: create(
|
service: create(
|
||||||
service(visitable, attribute, collection, text, consulIntentionList, catalogToolbar, tabgroup)
|
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)),
|
nodes: create(nodes(visitable, text, clickable, attribute, collection, popoverSelect)),
|
||||||
node: create(node(visitable, deletable, clickable, attribute, collection, tabgroup, text)),
|
node: create(node(visitable, deletable, clickable, attribute, collection, tabgroup, text)),
|
||||||
kvs: create(kvs(visitable, creatable, consulKvList)),
|
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 {
|
return {
|
||||||
visit: visitable('/:dc/services/:service/instances/:node/:id'),
|
visit: visitable('/:dc/services/:service/instances/:node/:id'),
|
||||||
externalSource: attribute('data-test-external-source', '[data-test-external-source]', {
|
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']),
|
tabs: tabs('tab', ['health-checks', 'upstreams', 'exposed-paths', 'addresses', 'tags-&-meta']),
|
||||||
checks: collection('[data-test-checks] li'),
|
checks: collection('[data-test-checks] li'),
|
||||||
upstreams: collection('[data-test-proxy-upstreams] > li', {
|
upstreams: alias('upstreamInstances.item'),
|
||||||
name: text('[data-test-destination-name]'),
|
upstreamInstances: upstreams(),
|
||||||
}),
|
|
||||||
exposedPaths: collection('[data-test-proxy-exposed-paths] > tbody tr', {
|
exposedPaths: collection('[data-test-proxy-exposed-paths] > tbody tr', {
|
||||||
combinedAddress: text('[data-test-combined-address]'),
|
combinedAddress: text('[data-test-combined-address]'),
|
||||||
}),
|
}),
|
||||||
|
|
Loading…
Reference in New Issue